美文网首页
UI绘制流程

UI绘制流程

作者: CodeJava | 来源:发表于2017-11-19 19:29 被阅读0次

    一、Activity里面去展示View的时候。
    进来先setContentView();
    getWindow().setContentView()

    一、从setContentView(R.layout.activity_main);入手了解UI的绘制起始过程
    1.Activity.java
    public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);//①
    initWindowDecorActionBar();
    }

    public abstract class Window {}
    Window有很多的类型。
    WindowManager. 不同的window toast phone input-method
    分很多的类型 根据类型分层级。
    mWindow = new PhoneWindow(this); PhoneWindow 隐藏的类
    installDecor();

    2.getWindow()拿到的是Window的实现类PhoneWindow

    PhoneWindow源码:
    com.android.internal.policy

    @Override
    public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    // 初始化Decor 导演
    if (mContentParent == null) {
       installDecor();//②
    }
    ……
      mLayoutInflater.inflate(layoutResID, mContentParent);//⑥
    }

    // 初始化
    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();//③生成一个DecorView(继承的FrameLayout)
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);//④
    }
    
    protected ViewGroup generateLayout(DecorView decor) {//⑤
    View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;
    }
    

    // 在SetContentView 之前要设置setRequestFuture.

    image.png image.png

    --------------UI绘制流程-----------------
    一、从setContentView(R.layout.activity_main);入手了解UI的绘制起始过程
    1.Activity.java
    public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);//①
    initWindowDecorActionBar();
    }

    2.getWindow()拿到的是Window的实现类PhoneWindow

    PhoneWindow源码:
    com.android.internal.policy

    @Override
    public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    if (mContentParent == null) {
    installDecor();//②
    }
    ……
    mLayoutInflater.inflate(layoutResID, mContentParent);//⑥
    }

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();//③生成一个DecorView(继承的FrameLayout)
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);//④
    }
    
    protected ViewGroup generateLayout(DecorView decor) {//⑤
    View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;
    }
    

    三、measure、layout、draw的三个执行流程
    View.java类
    measure:测量,测量自己有多大,如果是ViewGroup的话会同时测量里面的子控件的大小
    layout:摆放里面的子控件bounds(left,top,right,bottom)
    draw:绘制 (直接继承了view一般都会重写onDraw)

    ViewGroup.java

    看View.java类的源码:
    1.view的requestLayout()方法开始,递归地不断往上找父容器,最终找到DecorView
    2.执行了DecorView的ViewRootImp类的performTranversal()方法

    ------------------------ViewGroup.java总结:-----------------------
    一、measure的过程
    如何去合理的测量一颗View树?
    如果ViewGroup和View都是直接指定的宽高,我还要测量吗?
    正是因为谷歌设计的自适应尺寸机制(比如Match_parent,wrap_content),造成了宽高不确定,所以就需要进程测量measure过程。
    measure过程会遍历整颗View树,然后依次测量每一个View的真实的尺寸。(树的遍历--先序遍历)

    MeasureSpec:测量规格
    int 32位:010111100011100
    拿前面两位当做mode,后面30位当做值。
    1.mode:
    1) EXACTLY: 精确的。比如给了一个确定的值 100dp
    2) AT_MOST: 根据父容器当前的大小,结合你指定的尺寸参考值来考虑你应该是多大尺寸,需要计算(Match_parent,wrap_content就是属于这种)
    3) UNSPECIFIED: 最多的意思。根据当前的情况,结合你制定的尺寸参考值来考虑,在不超过父容器给你限定的只存的前提下,来测量你的一个恰好的内容尺寸。
    用的比较少,一般见于ScrollView,ListView(大小不确定,同时大小还是变的。会通过多次测量才能真正决定好宽高。)
    2.value:宽高的值。

    经过大量测量以后,最终确定了自己的宽高,需要调用:setMeasuredDimension(w,h)

    写自定义控件的时候,我们要去获得自己的宽高来进行一些计算,必须先经过measure,才能获得到宽高---不是getWidth(),而是getMeasuredWidth()
    也就是当我们重写onMeasure的时候,我们需要在里面调用child.measure()才能获取child的宽高。

    从规格当中获取mode和value:
    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    反过来将mode和value合成一个规格呢:
    MeasureSpec.makeMeasureSpec(resultSize, resultMode);

    ViewGroup:
    设计它的目的是什么?
    1)作为容器处理焦点问题。
    2)作为容器处理事件分发问题;
    3)控制容器添加View的流程:addView(),removeView()
    4)抽出了一些容器的公共的工具方法:measureChildren,measureChild,measureChildWidthMargins方法。

    image.png

    -------------------重点:-----------------------
    玩自定义控件的时候,需要进行测量measure,如何做好这件事?
    两种情况:
    1.继承自View的子类
    只需要重写onMeasure测量好自己的宽高就可以了。
    最终调用setMeasuredDimension()保存好自己的测量宽高。
    套路:
    int mode = MeasureSpec.getMode(widthMeasureSpec);
    int Size = MeasureSpec.getSize(widthMeasureSpec);
    int viewSize = 0;
    switch(mode){
    case MeasureSpec.EXACTLY:
    viewSize = size;//当前view的尺寸就为父容器的尺寸
    break;
    case MeasureSpec.AT_MOST:
    viewSize = Math.min(size, getContentSize());//当前view的尺寸就为内容尺寸和费容器尺寸当中的最小值。
    break;
    case MeasureSpec.UNSPECIFIED:
    viewSize = getContentSize();//内容有多大,久设置多大尺寸。
    break;
    default:
    break;
    }
    //setMeasuredDimension(width, height);
    setMeasuredDimension(size);

    2.继承自ViewGroup的子类:
        不但需要重写onMeasure测量自己,还要测量子控件的规格大小。
    
        可以直接使用ViewGroup的工具方法来测量里面的子控件,也可以自己来实现这一套子控件的测量(比如:RelativeLayout)
    套路:
        //1.测量自己的尺寸
        ViewGroup.onMeasure();
            //1.1 为每一个child计算测量规格信息(MeasureSpec)
            ViewGroup.getChildMeasureSpec();
            //1.2 将上面测量后的结果,传给每一个子View,子view测量自己的尺寸
            child.measure();
    
            //1.3 子View测量完,ViewGroup就可以拿到这个子View的测量后的尺寸了
            child.getChildMeasuredSize();//child.getMeasuredWidth()和child.getMeasuredHeight()
            //1.4ViewGroup自己就可以根据自身的情况(Padding等等),来计算自己的尺寸
            ViewGroup.calculateSelfSize();
        //2.保存自己的尺寸
        ViewGroup.setMeasuredDimension(size);
    

    二、layout的过程

    三、draw的过程

    作业:如何让一个ScrollView里面的ListView全部展开?
    有一种解决办法就是继承ListView,重写onMeasure方法:
    public void onMeasure(){
    int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
    super.onMeasure(widthMeasureSpec, expandSpec);
    }
    为什么要这么做?1.设置mode为 MeasureSpec.AT_MOST?2.value为Integer.MAX_VALUE >> 2?

    作业:
    1.热门标签自定义控件。

    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewGroup;
    
    public class RickyCustomView extends ViewGroup {
        private static final int OFFSET = 80;
    
        public RickyCustomView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            // 摆放
            int left = 0;
            int right = 0;
            int top = 0;
            int bottom = 0;
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                left = i*OFFSET;
                right = left + child.getMeasuredWidth();
                bottom = top + child.getMeasuredHeight();
                child.layout(left, top, right, bottom);
                
                top += child.getMeasuredHeight();
            }
    
        }
        
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
            
            int width = 0;
            int height = 0;
            //1.测量自己的尺寸
    //      ViewGroup.onMeasure();
            //1.1 为每一个child计算测量规格信息(MeasureSpec)
    //      ViewGroup.getChildMeasureSpec();
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                LayoutParams lp = child.getLayoutParams();
                int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
                int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
                //1.2 将上面测量后的结果,传给每一个子View,子view测量自己的尺寸
                child.measure(childWidthSpec, childHeightSpec);
            }
    //      //1.3 子View测量完,ViewGroup就可以拿到这个子View的测量后的尺寸了
    //      child.getChildMeasuredSize();//child.getMeasuredWidth()和child.getMeasuredHeight()
    //      //1.4ViewGroup自己就可以根据自身的情况(Padding等等),来计算自己的尺寸(mode,value)
    //      ViewGroup.calculateSelfSize();
            
            switch (widthMode) {
            case MeasureSpec.EXACTLY:
                width = widthSize;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    int widthAndOffset = i*OFFSET + child.getMeasuredWidth();
                    width = Math.max(width, widthAndOffset);
                }
                break;
            default:
                break;
            }
            
            
            switch (heightMode) {
            case MeasureSpec.EXACTLY:
                height = heightSize;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    height = height + child.getMeasuredHeight();
                }
                break;
            default:
                break;
            }
            //2.保存自己的尺寸
    //              ViewGroup.setMeasuredDimension(size);
            setMeasuredDimension(width, height);
        }
        
    
    }
    

    相关文章

      网友评论

          本文标题:UI绘制流程

          本文链接:https://www.haomeiwen.com/subject/nclyvxtx.html