ViewGroup的绘制源码学习笔记

ViewGroup

A ViewGroup is a special view that can contain other views. The view group is the base class for layouts and views containers. This class also defines the
android.view.ViewGroup.LayoutParams class which serves as the base class for layouts parameters.

Overview

首先看下ViewGroup的继承层次:

public abstract class ViewGroup extends View implements ViewParent, ViewManager 

可以看到ViewGroup与View第一个不同点在于,ViewGroup是一个抽象类,继承自View,实现了ViewParent和ViewManager两个接口

ViewParent和ViewManager

首先是ViewParent这个接口,内含的部分方法如下:


ViewParent部分方法

其次是ViewManager的方法:


ViewManager

ViewManager的方法很好理解,对View进行的增删改3个操作

构造方法

和View类似,ViewGroup也有4个构造函数:

public ViewGroup(Context context)
public ViewGroup(Context context, AttributeSet attrs)
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr)
public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)

但是实际上,前3个构造函数最终也只是调用到第4个,而第4个构造函数的实现也非常简单明了:

public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);

        initViewGroup();
        initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
    }

即两个init方法的调用:

private void initViewGroup() {
        // ViewGroup doesn't draw by default
        if (!debugDraw()) {
            setFlags(WILL_NOT_DRAW, DRAW_MASK);
        }
        mGroupFlags |= FLAG_CLIP_CHILDREN;
        mGroupFlags |= FLAG_CLIP_TO_PADDING;
        mGroupFlags |= FLAG_ANIMATION_DONE;
        mGroupFlags |= FLAG_ANIMATION_CACHE;
        mGroupFlags |= FLAG_ALWAYS_DRAWN_WITH_CACHE;

        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB) {
            mGroupFlags |= FLAG_SPLIT_MOTION_EVENTS;
        }

        setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);

        mChildren = new View[ARRAY_INITIAL_CAPACITY];
        mChildrenCount = 0;

        mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
    }

首先initViewGroup()方法对其自身的一些标志位等做了初始操作;然后调用initFromAttributes(),从函数名称也可以猜到,这个函数作用是从xml属性中进行初始化操作:

private void initFromAttributes(
            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ViewGroup, defStyleAttr,
                defStyleRes);

        final int N = a.getIndexCount();
        for (int i = 0; i < N; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case R.styleable.ViewGroup_clipChildren:
                    setClipChildren(a.getBoolean(attr, true));
                    break;
                case R.styleable.ViewGroup_clipToPadding:
                    setClipToPadding(a.getBoolean(attr, true));
                    break;
                case R.styleable.ViewGroup_animationCache:
                    setAnimationCacheEnabled(a.getBoolean(attr, true));
                    break;
                ……
                case R.styleable.ViewGroup_touchscreenBlocksFocus:
                    setTouchscreenBlocksFocus(a.getBoolean(attr, false));
                    break;
            }
        }

        a.recycle();
    }

绘制相关

从View的绘制源码学习中已经知道,一个View的绘制,包括measure/layout/draw三个阶段,那么作为View的子类,ViewGroup的绘制也无非这3个阶段:

measure

因为View的measure()方法是个final方法,因此在ViewGroup中不能重写改方法,而ViewGroup中也没有实现onMeasure()方法,但在measure阶段,ViewGroup提供了几个measure相关的方法:

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec)
protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec)
protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) 

这几个方法在实现上,关键的点是一致的,因此选择相对简洁的measureChild()来看:

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

最关键的点在于最后一句,child.measure(),从View绘制过程已经知道,measure阶段的目的在于,让View得到自己所占据的大小,因此对ViewGroup来说,measure阶段,就需要让自己的所有子View知道自己的大小。

layout

在layout阶段,ViewGroup重写了View的layout()和onlayout()方法

实际上layout并没有做什么实质的工作:

public final void layout(int l, int t, int r, int b) {
        if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
            if (mTransition != null) {
                mTransition.layoutChange(this);
            }
            super.layout(l, t, r, b);
        } else {
            // record the fact that we noop'd it; request layout when transition finishes
            mLayoutCalledWhileSuppressed = true;
        }
    }

更加关键的是onLayout()方法,这里变成了abstract抽象方法,这也是为什么ViewGroup是一个抽象类的原因

    protected abstract void onLayout(b
            oolean changed,
            int l, int t, int r, int b);
draw

在draw阶段,ViewGroup里相关的函数是dispatchDraw(),drawChild():在dispatchDraw()中遍历所有的子View,对所有子View执行drawChild()方法,而drawChild()方法的实现非常简单:

protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }

ViewGroup的绘制,最终也就是各个子View自身的绘制。

总结

最后总结一下ViewGroup和View的差异:

  • View是一个普通类,而ViewGroup是一个抽象类
  • measure阶段,View通过measure()和onMeasure()来确定自身的占据范围,ViewGroup通过measureChild()等方法来确定各个子View的占据范围
  • layout阶段,View通过layout()和onLayout()函数来确定位置,而ViewGroup中onLayout()变成抽象方法,需要子类实现;
  • draw阶段,View通过draw()和onDraw()方法来实现绘制,ViewGroup通过dispatchDraw()和drawChild()方法来让各个子View绘制自身。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343