Android中常会自定义一个控件,这里详细说一下view的绘制流程。(主要是敲一遍源码加强记忆)
首先view主要有两种:view和viewGroup(详细介绍不再赘述),从新建一个activity开始,我们会用setContentView展开一个内容视图,这就是一个viewGroup,也是用户所看到的界面。首先每个activity都会创建出一个PhoneWindow窗口,这是内容的最基本窗口,也是activity和view的交互接口。在PhoneWindow的内层是一个DecorView,这个本质上就是一个FrameLayout,也就是最基本的一个view。
开始view的绘制,首先要从ViewRoot的performTraversals()方法开始,从最顶层的viewGroup开始向下层遍历。view控件就自己绘制,viewGroup会通知下层的view进行绘制操作。
private void performTraversals(){
int childWidthMeasureSpec = getRootMeasureSpec(mWidth,lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight,lp.height);
...
//测量
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec);
//布局
performLayout(lp,desiredWindowWidth,desiredWindowHeight);
//绘制
performDraw();
}
performTraversals()这个方法的主要在里面执行了三个方法,也就是视图绘制的是那个步骤:测量(Measure)布局(Layout)和绘制(Draw)。顾名思义,测量就是计算视图的大小位置等情况,布局是决定在父控件中的位置,绘制就是画图过程了。
在测量过程中有一个MeasureSpec类。这是View类的一个静态内部类,用来说明测量view的方式。它表示一个32位的整形值,头两位表示测量模式(SpecMode),后30位说明如何测量这个view。核心代码如下
package com.example.myapplication;
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
//不指定测量模式
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
//精确测量模式
public static final int EXACTLY = 1 << MODE_SHIFT;
//最大值测量模式
public static final int AT_MOST = 0 << MODE_SHIFT;
//根据指定大小和模式创建MeasureSpec
public static int makeMeasureSpec(int size, int mode){
if (sUseBrokenMakeMeasureSpec){
return size + mode;
}else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//微调某个MesureSpec的大小
static int adjust(int MeasureSpec, int delta){
final int mode = getMode(measureSpec);
if (mode == UNSPECIFIED) {
//不用为UNSPECIFIED微调大小
return makeMeasureSpec(0, UNSPECIFIED);
}
int size = getSize(measureSpec) + delta;
if(size < 0){
Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size + ")spec: " + toString(measureSpec) + "delta: " + delta);
size = 0;
}
return makeMeasureSpec(size,mode);
}
}
其实这段源码敲出来没啥作用,主要是为了加深我自己的记忆,(这东西确实毫无作用)。这里主要是三种测量模式,UNSPECIFIED:父视图不限制子视图大小。通常用于系统内部。
EXACTLY:当视图的layout_width或者layout_height设为match_parent时生效,表示父视图精确了子视图的大小,这时候View的测量值就是SpecSize的大小。
AT_MOST:当属性为wrap_content的时候生效,这时子视图的大小可以是不超过父视图的最大尺寸的任意大小。
对之前的Decor而言,MeasureSpec由窗口大小和自身的LayoutParams决定,而普通view的MeasureSpec由父视图的MeasureSpec和自身的LayoutParams决定。
这里是进行测量值的计算,然后是进行测量操作。
测量是从preformMeasure()方法开始。
private void performMeasure(int childWidthMeasureSpec,int childHeightMeasureSpec){
mView.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}
//遍历测量ViewGroup中所有的view
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec){
...
measureChild(child,widthMeasureSpec,heightMeasureSpec);
}
//测量某个指定的view
protected void measureChild(View child,int widthMeasureSpec, int heightMeasureSpec){
...
child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}
在这里面开始为viewGroup分发测量操作,viewGroup在其中的measureChild方法中传给子view。
view或者viewGroup的onMeasure方法就如下,最终测量是回调onMeasure方法实现的,这个通常由view的特定子类实现,我们可以重写onMeasure方法自定义测量过程。如果不重写则会默认使用getDefaultSize()这个方法测量
public final void measure(int widthMeasureSpec, int heightMeasureSpec){
onMeasure(widthMeasureSpec,heightMeasureSpec);
}
//自定义测量过程就重写onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
...
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec);
}
public static int getDefaultSize(int size,int measureSpec);
布局过程就用于确定view在父控件中的位置,由父控件获取子view的位置参数后调用子view的layout方法实现。performLayout的代码如下:
//ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int
desiredWindowWidth,int desiredWindowHeight){
...
host.layout(0,0,host.getMeasuredWidth(),host.getMeasuredHeight());
}
//View.java
public void layout (int l, int t, int r, int b){
...
onLayout(changed,l,t,r,b);
}
//空方法,用于重写
protected void onLayout(boolean changed, int left, int top, int right, int bottom){
}
子类如果是viewGroup,则重写onLayout方法,实现viewGroup中所有view的布局。
最后的Draw过程用于绘制控件。从performDraw()方法开始。
private void performDraw(){
...
draw(fullRedrawNeeded);
}
private void draw(boolean fullRedrawNeeded){
...
if(!drawSoftware(surface, mAttachinfo, xOffset,
yOffset,scalingRequired, dirty)){
return;
}
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo,
int xoff, int yoff, boolean scalingRequired, Rect dirty){
...
mView.draw(canvas);
}
最终调用的是每个view的draw方法进行绘制。绘制的draw方法内主要由六个步骤,对应draw中调用的六个方法,在此不一一列举了,主要有:
1.绘制背景
2.保存canvas图层
3.绘制view内容
4.绘制子控件的view
5.绘制view的fading并恢复图层
6.绘制view的装饰(滚动条等)
/***
*本篇全部为课本内容,没有任何个人看法和其他资料内容掺杂
*源码全部都是部分代码,并没有把全部内容敲出来(太累了)
*本篇只会在面试题中出现,实际使用价值,emmm,自定义view不太需要这么多内容吧
***/