Android常考问题(3)-view的绘制

12 篇文章 0 订阅
10 篇文章 0 订阅

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不太需要这么多内容吧

***/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值