UI绘制流程

一、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);
    }
    

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容