6.2 即时通讯原理:
即时通讯(Instant Messenger,简称IM):是一种使人们能在网上识别在线用户并与他们实时交换消息的技术,是电子邮件发明以来迅速崛起的在线通讯方式。
IM完全基于TCP/IP和UDP进行通讯的,TCP/IP和UDP都是建立在更低层的IP协议上的两种通讯传输协议。前者是以数据流的形式,将传输数据经分割、打包后,通过两台机器之间建立起的虚电路,进行连续的、双向的、严格保证数据正确性的文件传输协议。而后者是以数据报的形式,对拆分后的数据的先后到达顺序不做要求的文件传输协议。
QQ就是使用UDP协议进行发送和接收消息的。当你的机器安装了OICQ以后,实际上,你既是服务端(Server),又是客户端(Client)。当你登录OICQ时,你的OICQ作为Client连接到腾讯公司的主服务器上,当你“看谁在线时,你的OICQ又一次作为Client从QQ Server上读取在线网友名单。当你和你的OICQ伙伴进行聊天时,如果你和对方的连接比较稳定,你和他的聊天内容都是以UDP的形式,在计算机之间传送。如果你和对方的连接不是很稳定,QQ服务器将为你们的聊天内容进行中转。其他的即时通信软件原理与此大同小异。
一般的步骤:
首先,用户A输入自己的用户名和密码登录即时通讯服务器,服务器通过读取用户数据库来验证用户身份,如果用户名、密码都正确,就登记用户A的IP地址、IM客户端软件的版本号及使用的TCP/UDP端口号,然后返回用户A登录成功的标志,此时用户A在 IM系统中的状态为在线(Online Presence)。
其次,根据用户A存储在IM服务器上的好友列表(Buddy List),服务器将用户A在线的相关信息发送到也同时在线的即时通讯好友的PC机,这些信息包括在线状态、IP地址、 IM客户端使用的TCP端口(Port)号等,即时通讯好友PC机上的即时通讯软件收到此信息后将在PC桌面上弹出一个小窗口予以提示。
第三步,即时通讯服务器把用户A存储在服务器上的好友列表及相关信息回送到他的PC机,这些信息包括也在线状态、IP地址、IM客户端使用的TCP端口(Port)号等信息,用户A的PC机上的IM客户端收到后将显示这些好友列表及其在线状态。
接下来,如果用户A想与他的在线好友用户B聊天,他将直接通过服务器发送过来的用户B的IP地址、TCP端口号等信息,直接向用户B的PC机发出聊天信息,用户B的IM客户端软件收到后显示在屏幕上,然后用户B再直接回复到用户A的PC机,这样双方的即时文字消息就不通过 IM服务器中转,而是通过网络进行点对点的直接通讯,这称为对等通讯方式(Peer To Peer)。在商用即时通讯系统中,如果用户A与用户B的点对点通讯由于防火墙、网络速度等原因难以建立或者速度很慢, IM服务器还提供消息中转服务,即用户A和用户B的即时消息全部先发送到IM服务器,再由服务器转发给对方。早期的IM系统,在IM客户端和IM服务器之间通讯采用采用UDP协议,UDP协议是不可靠的传输协议,而在 IM客户端之间的直接通讯中,采用具备可靠传输能力的TCP协议。随着用户需求和技术环境的发展,目前主流的即时通讯系统倾向于在即时通讯客户端之间、即时通讯客户端和即时通讯服务器之间都采用TCP协议。
6.3 聊天布局实现
聊天界面其实就是一个复杂的ListView,在数据适配器中,从application中获取到当前登录的用户,与聊天消息中的from参数进行比较,如果两者相同则说明是自己发送的消息,那么ListView中就显示发送消息的布局,否则显示接收消息的布局。适配器的代码如下:
public class ChartMessageAdapter extends ArrayAdapter<QQMessage> {
ImApp app;
public ChartMessageAdapter(Context context, List<QQMessage> objects) {
super(context, 0, objects);
Activity activity = (Activity) context;
app = (ImApp) activity.getApplication();
}
/**
* 根据集合中的position位置,返回不同的值,代表不同的布局。
*
*/
@Override
public int getItemViewType(int position) {
QQMessage msg = getItem(position);
// 消息来自谁,如果消息来自我自己,说明是我发送的
if (msg.from == app.getMyAccount()) {
// 代表自己发送的消息
return 0;
} else {
//代表接收到的消息
return 1;
}
}
/**
* 两种布局
*/
@Override
public int getViewTypeCount() {
return 2;
}
class ViewHolder {
TextView time;
TextView content;
ImageView head;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
int type = getItemViewType(position);
if (0 == type) {
// 发送的布局
ViewHolder holder;
if (convertView == null) {
convertView = View.inflate(getContext(),
R.layout.item_chat_send, null);
holder = new ViewHolder();
holder.time = (TextView) convertView.findViewById(R.id.time);
holder.content = (TextView) convertView
.findViewById(R.id.content);
holder.head = (ImageView) convertView.findViewById(R.id.head);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// 设置值
QQMessage msg = getItem(position);
holder.time.setText(msg.sendTime);
holder.head.setImageResource(msg.fromAvatar);
holder.content.setText(msg.content);
return convertView;
} else {
// 接收的布局
ViewHolder holder;
if (convertView == null) {
convertView = View.inflate(getContext(),
R.layout.item_chat_receive, null);
holder = new ViewHolder();
holder.time = (TextView) convertView.findViewById(R.id.time);
holder.content = (TextView) convertView
.findViewById(R.id.content);
holder.head = (ImageView) convertView.findViewById(R.id.head);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
// 设置值
QQMessage msg = getItem(position);
holder.head.setImageResource(msg.fromAvatar);
holder.time.setText(msg.sendTime);
holder.content.setText(msg.content);
return convertView;
}
}
}
关于QQ聊天界面更多详情可参考以下资料:
http://blog.youkuaiyun.com/baiyuliang2013/article/details/39551519
private void updateView(int itemIndex) {
//得到第一个可显示控件的位置,
int visiblePosition = mListView . getFirstVisiblePosition ();//只有当要更新的view在可见的位置时才更新,不可见时,跳过不更新if (itemIndex - visiblePosition >= 0) {
//得到要更新的item的view
View view = mListView.getChildAt(itemIndex - visiblePosition);
//调用adapter更新界面
mAdapter.updateView(view, itemIndex);
}
}
8、图片的放大缩小原理是什么?
1)Handle事件分发机制
2)Matrix矩阵
3)ScaleGuestureDetector:手势缩放
4)GuestureDetector:手势移动
5)postdelay + runnable:实现缩放动画
6)根据矩阵Matrix获得x,y轴的缩放值
7)放大后的图片宽度大于屏幕宽度时,父控件消费掉事件
if (rectF.width() > getWidth()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
9、项目中是如何加载图片的?
针对这三种情况我们一般使用BitmapFactory的:decodeStream, decodeFile,decodeResource,这三个函数来获取到bitmap然后再调用ImageView的setImageBitmap函数进行展现。
1. 测量过程由上至下,在measure过程的最后,每个视图将存储自己的尺寸大小和测量规格。
setMeasuredDimension 是测量阶段的终极方法,在onMeasure()方法中调用,将计算得到的尺寸,传递给该方法,测量阶段结束。在自定义视图时,不需要关心系统复杂的Measure过程,只需要调用setMeasuredDimension()设置根据MeasureSpec计算得到的尺寸即可。同时,onMeasure()方法也必须调用setMeasuredDimension()方法来设置重新测量。
onMeasure被调用的时候传入两个参数widthMeasureSpec和heightMeasureSpec。每个都是一个32位的int值它分成两个部分高两位为测量模式,低30位为测量的大小。
我们可以通过:
int mode = MeasureSpec.getMode(xxxxxxx)获取到模式,
int size = MeasureSpec.getSize(xxxxxxx)获取到尺寸数值。
测量模式有三种:
EXACTLY:精确模式,当我们将layout_height以及layout_width设置为具体数值时,或者设置为match_parent的时候,使用的是这种模式
AT_MOST:最大值模式,当控件的layout_height以及layout_width被设置为wrap_content属性的时候,使用的是这种模式,这种模式下控件的尺寸只要不超过父控件允许的最大尺寸即可。
UNSPECIFIED:这种情况我见得不是很多,基本上没有用到。
View 默认情况下支持EXACTLY模式,因此如果不重写onMeasure方法时,只能使用EXACTLY模式,所定义的View只能指定具体尺寸,或者是match_parent,而不能是wrap_content.如果需要支持wrap_content就必须重写onMeasure方法。
1. 子视图的具体位置都是相对于父视图而言的。View的onLayout()方法为空实现,而ViewGroup的onLayout为abstract,因此,自定义的View要继承ViewGroup时,必须实现onLayout函数。
2. 在Layout过程中,子视图会调用getMeasuredWidth()和getMeasuredHeight()方法获取到measure过程得到mMeasuredWidth和mMeasuredHeight,作为自己的width和height。然后调用每一个子视图的layout(),来确定每个子视图在父视图中的位置。
1. 所有视图最终都是调用View的draw方法进行绘制。 在自定义视图中, 也不应该复写该方法, 而是复写onDraw()方法进行绘制, 如果自定义的视图确实要复写该方法,先调用super.draw()完成系统的绘制,再进行自定义的绘制。
2. onDraw()方法默认是空实现,自定义绘制过程需要复写方法,绘制自身的内容。
3. dispatchDraw()发起对子视图的绘制,在View中默认为空实现,ViewGroup复写了dispatchDraw()来对其子视图进行绘制。自定义的ViewGroup不应该对dispatchDraw()进行复写。作者:wusp
View的绘制:
我们知道自定义一个View需要继承自View并重写构造方法以及onDraw方法。
般我们在Android Studio创建一个View的时候,会要求复写构造方法,默认情况下会有三个构造函数:
public class CustomView extends View {
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.CustomizeStyleRef);
}
public CustomView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
}
11、圆形进度条间显示文字怎么实现?
11.1 通过动画实现
定义res/anim/loading_anim.xml
<?xml version="1.0" encoding="UTF-8"?>
<animation-list android:oneshot="false"
xmlns:android="http://schemas.android.com/apk/res/android">
<item android:duration="150" android:drawable="@drawable/loading_01" />
<item android:duration="150" android:drawable="@drawable/loading_02" />
<item android:duration="150" android:drawable="@drawable/loading_03" />
<item android:duration="150" android:drawable="@drawable/loading_04" />
<item android:duration="150" android:drawable="@drawable/loading_05" />
<item android:duration="150" android:drawable="@drawable/loading_06" />
<item android:duration="150" android:drawable="@drawable/loading_07" />
</animation-list>
在layout文件中引用
<ProgressBar
android:id="@+id/progressBar1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="20dip"
android:layout_marginTop="20dip"
android:indeterminate="false"
android:indeterminateDrawable="@anim/loading_anim" />
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360" >
<shape
android:innerRadiusRatio="3"
android:shape="ring"
android:thicknessRatio="8"
android:useLevel="false" >
<gradient
android:centerColor="#FFDC35"
android:centerY="0.50"
android:endColor="#CE0000"
android:startColor="#FFFFFF"
android:type="sweep"
android:useLevel="false" />
</shape>
</rotate>
<ProgressBar
android:id="@+id/progressBar2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="false"
android:indeterminateDrawable="@drawable/loading_color" />
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >
<item>
<rotate
android:drawable="@drawable/exchange_loading"
android:fromDegrees="0.0"
android:pivotX="50.0%"
android:pivotY="50.0%"
android:toDegrees="360.0" />
</item>
</layer-list>
<ProgressBar
android:id="@+id/progressBar3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="false"
android:indeterminateDrawable="@drawable/lodaing_img"
/>
<!--?xml version="1.0" encoding="utf-8"?-->
<resources>
<declare-styleable name="CircleProgress">
<!-- 默认圆实心的颜色 -->
</ attr ></ attr >
<!-- 默认圆边框的颜色 -->
<!-- 默认圆边框的宽度 -->
<!-- 默认圆的半径 -->
<!-- 进度条的颜色 -->
<!-- 进度条的宽度 -->
<!-- 小圆的实心颜色 -->
<!-- 小圆的边框颜色 -->
<!-- 小圆的边框宽度 -->
<!-- 小圆的半径 -->
<!-- 文字的颜色 -->
<!-- 文字的字体大小 -->
</declare-styleable>
</resources>
public CircleProgress(Context context) {
this(context,null);
}
public CircleProgress(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CircleProgress(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
//获取自定义属性
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.CircleProgress);
.........}
private void setPaint() {
//默认圆
defaultCriclePaint = new Paint();
defaultCriclePaint.setAntiAlias(true);//抗锯齿
defaultCriclePaint.setDither(true);//防抖动
defaultCriclePaint.setStyle(Paint.Style.STROKE);
defaultCriclePaint.setStrokeWidth(defaultCircleStrokeWidth);
defaultCriclePaint.setColor(defaultCircleStrokeColor);//这里先画边框的颜色,后续再添加画笔画实心的颜色
//默认圆上面的进度弧度
progressPaint = new Paint();
progressPaint.setAntiAlias(true);
progressPaint.setDither(true);
progressPaint.setStyle(Paint.Style.STROKE);
progressPaint.setStrokeWidth(progressWidth);
progressPaint.setColor(progressColor);
progressPaint.setStrokeCap(Paint.Cap.ROUND);//设置画笔笔刷样式
//进度上面的小圆
smallCirclePaint = new Paint();
smallCirclePaint.setAntiAlias(true);
smallCirclePaint.setDither(true);
smallCirclePaint.setStyle(Paint.Style.STROKE);
smallCirclePaint.setStrokeWidth(smallCircleStrokeWidth);
smallCirclePaint.setColor(smallCircleStrokeColor);
//画进度上面的小圆的实心画笔(主要是将小圆的实心颜色设置成白色)
smallCircleSolidePaint = new Paint();
smallCircleSolidePaint.setAntiAlias(true);
smallCircleSolidePaint.setDither(true);
smallCircleSolidePaint.setStyle(Paint.Style.FILL);
smallCircleSolidePaint.setColor(smallCircleSolideColor);
//文字画笔
textPaint = new Paint();
textPaint.setAntiAlias(true);
textPaint.setDither(true);
textPaint.setStyle(Paint.Style.FILL);
textPaint.setColor(textColor);
textPaint.setTextSize(textSize);
}
// 如果该View布局的宽高开发者没有精确的告诉,则需要进行测量,如果给出了精确的宽高则我们就@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize;
int heightSize;
int strokeWidth = Math.max(defaultCircleStrokeWidth, progressWidth);
if(widthMode != MeasureSpec.EXACTLY){
widthSize = getPaddingLeft() + defaultCircleRadius*2 + strokeWidth + getPaddingRight();
widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
}
if(heightMode != MeasureSpec.EXACTLY){
heightSize = getPaddingTop() + defaultCircleRadius*2 + strokeWidth + getPaddingBottom();
heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
canvas.translate(getPaddingLeft(), getPaddingTop());
//画默认圆
canvas.drawCircle(defaultCircleRadius,defaultCircleRadius,defaultCircleRadius,defaultCriclePaint);
//画进度圆弧
currentAngle = getProgress()*1.0f/getMax()*360;
canvas.drawArc(new RectF(0,0,defaultCircleRadius*2,defaultCircleRadius*2),mStartSweepValue, currentAngle ,false,progressPaint);
//画中间文字
String text = getProgress()+"%";
//获取文字的长度的方法
float textWidth = textPaint.measureText(text );
float textHeight = (textPaint.descent() + textPaint.ascent()) / 2;
canvas.drawText(text, defaultCircleRadius-textWidth/2, defaultCircleRadius-textHeight, textPaint);
canvas.restore();
}
public class MainActivity extends AppCompatActivity {
private CircleProgress circleProgress;
private int progress;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case HANDLER_MESSAGE:
progress = circleProgress.getProgress();
circleProgress.setProgress(++progress);
if(progress >= 100){
handler.removeMessages(HANDLER_MESSAGE);
progress = 0;
circleProgress.setProgress(0);
}else{
handler.sendEmptyMessageDelayed(HANDLER_MESSAGE, 100);
}
break;
}
}
};
public static final int HANDLER_MESSAGE = 2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
circleProgress = (CircleProgress) findViewById(R.id.circleProgress);
circleProgress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message message = Message.obtain();
message.what = HANDLER_MESSAGE;
handler.sendMessage(message);
}
});
}
}