自定义ViewGroup—回顾

自定ViewGroup要比自定义View要复杂一点,因为自定义ViewGroup不仅测量自身还要测量子元素和及重写onLayout()来一次排列子View。下面这篇文章是关于自定义ViewGroup的一些基本知识,这些主要内容来自《android开发艺术探索》,在文章最后又这本书的网上版本。

image

目录

  • ViewGroup的measure过程
  • onMeasure()函数
  • onLayout()函数
  • 对Padding和Margin的处理
  • 在Activity中获取View的宽高

ViewGroup的measure过程

ViewGroup是一个抽象类,他没有重写View的onMeasure()方法。因此并没有定义具体的测量过程,具体的测量过程交给了他的子类来完成,比如:LinearLayoutRelativeLayout等。ViewGroup提供了一个measureChildren的方法来测量子View:

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            //测量子元素
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

下面为measureChild()的源码:

protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
    final LayoutParams lp = child.getLayoutParams();
    //获取子元素宽度MeasureSpec
    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    //获取子元素高度MeasureSpec        
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);
    //子元素进行测量
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

measureChild()方法会先获取子View的LayoutParams参数,然后再通过getChildMeasureSpec()获取子View的宽高MeasureSpec,最后将获取到的MeasureSpec传递给view的()方法进行测量。具体的执行过程我在《View的绘制流程》这篇文章中介绍过,这里就不在多少说了。

onMeasure()方法

因为ViewGroup没有重写View的onMeasure方法,我们在自定义的时候集成了ViewGruppo成了View的子类,因此要写自己布局的测量过则。那我上篇文章《自定义ViewGroup—FlowLayout》中的部分代码为例:

@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
   ...
                          //##1
    //循环遍历子View
    for (int i = 0; i < getChildCount(); i++) {
        View childView = getChildAt(i);

        //先测量子View
        measureChild(childView, widthMeasureSpec, widthMeasureSpec);
        ....
        //计算剩余空间
        remaining = widthSize - usedWidth;
                        //##2
        //判断子view的测量宽度是否大于剩余空间,如果大于则另起一行
        if (childWidth > remaining) {
            //另起一行,已用宽度应该为零
            usedWidth = 0;
            //添加View
            mLineView = new LineView();
            mLineViewList.add(mLineView);
        }
        mLineView.addView(childView);
        //已用宽度累加
        usedWidth += childWidth;
        mLineView.setTotalWidth(usedWidth);
    }
                     //##3
    for (int i = 0; i < mLineViewList.size(); i++) {
        //总高度=所有行数相加
        totalHeight += mLineViewList.get(i).mHeight;
    }
               
    //父容器的总高度=上下padding的高度,在最后额外加一个底部marginBottom
    totalHeight += getPaddingTop() + getPaddingBottom() + marginBottom;
                     //##4
    setMeasuredDimension(widthSize, heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight);
}

上面代码中主要是测量FlowLayout的高度。它是通过遍历子View(//##1)计算剩余宽度,再通过子View的宽度和剩余宽度比较来判断是否换行(//##2)。FlowLayout的高度如果是在warp_cotent模式下高度就为子View的行数乘上子View的高度(//##3),最后通过setMeasuredDimension()计算View的宽高并保存起来。

onLayout()函数

onLayout()函数式是ViewGroup的一个抽象函数,ViewGroup的子类必须实现该函数,用于定义View的摆放规则。

注:该方法有一个指的探讨的问题,下面onLayout()是被@Override注释着的,也就是这个方法是复用了View的onLayout()方法。那么问题来了,java中父类的方法是否能把子类重写为抽象方法?这个问题我在好多技术群中向大神请教过,有的说能有的说不能。后来自己在项目中亲自测试,发现可以重写但是不能调用。如果读者知道这个问题,欢迎在下方留言。

@Override
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);

该方法的调用是在View的layout被调用,我们可以查看ViewGroup的layout()放知道ViewGroup的layout过程是交给父类完成的。

@Override
public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        //调用父类的layout方法
        super.layout(l, t, r, b);
    } else {
         transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}

View的layout方法中调用了onLayout()方法

@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
    ...
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
       ...
    }
  ...
}

对Padding和Margin的处理

Padding的处理比较简单,只需要getPaddingXXX()来获取padding的值,在计算ViewGroup的宽高的时候将其加上即可:

paddingLeft = getPaddingLeft();
paddingTop = getPaddingTop();
paddingRight = getPaddingRight();
paddingBottom = getPaddingBottom();
//ViewGroup宽高计算
viewGroupWidth = paddingLeft + viewsWidth + paddingRight;
viewGroupHeight = paddingTop + viewsHeight + paddingBottom;

Margin的处理比较麻烦一点,首先他要先从子View中获取layoutParams属性,通过子View的LayoutParams属性来获取设置的Margin值。其layoutParams获取方法为childView.getLayoutParams()。要注意下面两点:

  1. 获取的要是MarginLayoutParams()类型,记得做类型强转。
  2. 重新generateLayoutParams(),返回类型为MarginLayoutParams类或他的子类。否则回报类型转换异常

下面为实现代码:

@SuppressLint("DrawAllocation")
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    //获取Margin值
    MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
    marginLeft = Math.max(marginLeft, lp.rightMargin);
    marginTop = Math.max(marginTop, lp.topMargin);
    marginRight = Math.max(marginRight, lp.rightMargin);
    marginBottom = lp.bottomMargin;
    
    //计算子View四个坐标位置
    int cLeft = left + marginLeft;
    int cRight = left + childWidth + marginRight;
    int cTop = top + marginTop;
    int cBottom = top + childHeight + marginBottom;
    //设置View的具体位置
    childView.layout(cLeft, cTop, cRight, cBottom);
}
//重写generateLayoutParams()
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new MarginLayoutParams(getContext(), attrs);
}

在Activity中获取View的宽高

android中不能再Activity的生命周期onCreate()onStart()onResume()生命周期中获取到View的宽高,这是因为Activity的生命周期和View的测量过程不是同步执行的。对于上面的问题有四种解决方案。下面为三种解决方法,第四种方案比较复杂就没写出来。

onWindowFoucusChanged()中获取

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    int width = mFlowL_testCustom.getMeasuredWidth();
    int height = mFlowL_testCustom.getMeasuredHeight();
    Log.i(TAG, "onWindowFocusChanged()测量的宽为:" + width + "高为:" + height);
}

通过View的post方法,经请求发送到消息队列中执行。

mFlowL_testCustom.post(new Runnable() {
    @Override
    public void run() {
        int width = mFlowL_testCustom.getMeasuredWidth();
        int height = mFlowL_testCustom.getMeasuredHeight();
        Log.i(TAG, "mFlowL_testCustom.post()测量的宽为:" + width + "高为:" + height);
    }
});

通过为ViewTreeObserver添加OnGlobalLayoutListener()来实现

ViewTreeObserver treeObserver=mFlowL_testCustom.getViewTreeObserver();
    treeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            int width = mFlowL_testCustom.getMeasuredWidth();
            int height = mFlowL_testCustom.getMeasuredHeight();
            Log.i(TAG, "addOnGlobalLayoutListener()测量的宽为:" + width + "高为:" + height);
        }
});

总结

文章先写到这里吧!最近一直在坚持每天写技术笔记,希望能慢慢将这种坚持当成一种习惯。最后祝所有看到这篇文章的人工作顺利,工资翻番。

参考

Android开发艺术探索完结篇——天道酬勤

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

推荐阅读更多精彩内容