自定义布局

本文深入探讨了自定义View布局的原理,包括测量和布局阶段的详细流程,以及如何通过重写onMeasure和onLayout方法来自定义View和ViewGroup的尺寸和内部布局。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自定义View布局
1.确定每个View的位置和尺寸
2.作用:为绘制和触摸范围做支持
1.对于绘制:知道自己需要在哪里绘制。
2.对于触摸反馈:知道用户的点是在哪里。
自定义View布局的工作内容
自定义View的工作分为两个阶段:测量阶段和布局阶段

测量流程:从上到下递归调用每个View或者ViewGroup的measure()方法,测量他们的尺寸并计算他们的位置。
布局阶段:从上到下递归地调用每个View或者ViewGroup的layout()方法,把测量得到的他们的尺寸和位置赋值给他们。
View或者ViewGroup的布局过程

1.测量阶段:measure()方法被父View调用,在measure()中做一些准备和优化工作后,调用onMeasure()来进行实际的自我测量。onMeasure()做的事情,在View和ViewGroup中是不一样的。

1.View:View在onMeasure()中会计算出自己的尺寸然后保存;
2.ViewGroup:ViewGroup在onMeasure()中会调用所有子View的measure()让他们进行自我测量,并根据子View计算出的期望尺寸来计算出他们的实际尺寸(实际上99.99%的父View都会使用子View给出的期望尺寸来作为实际尺寸),然后保存。

2.布局阶段:layout()方法被父View调用,在layout()中他会保存父View传进来的自己的位置和尺寸,并且调用onLayout()来进行实际的内部布局。onLayout()做的事,View和ViewGroup也不一样:

1.View:由于没有子View,所以View的onLayout()什么也不需要做。
2.ViewGroup:ViewGroup在onLayout()中会调用自己的所有的子View的layout()方法,把它们的尺寸和位置传递给他们,并且让他们自己完成自己内部的布局。

布局过程自定义的方式
布局过程自定义的方式有三类
1.重写onMeasure()来修改已有的View的尺寸。
2.重写onMeasure()来全新定制自定义View的尺寸。
3.重写onMeasure()和onLayout()来全新定制自定义ViewGroup的内部布局。
具体做法
继承已有的 View,简单改写它们的尺⼨寸:重写 onMeasure():SquareImageView

  1. 重写 onMeasure()

  2. ⽤用 getMeasuredWidth() 和 getMeasuredSize() 获取到测量量出的尺⼨寸 3. 计算出最终要的尺⼨寸

  3. ⽤用 setMeasuredDimension(width, height) 把结果保存

     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
         int width = getMeasuredWidth();
         int height = getMeasuredHeight();
         int size = Math.min(width, height);
         setMeasuredDimension(size, size);
     }
    

对⾃自定义 View 完全进⾏行行⾃自定义尺⼨寸计算:重写 onMeasure():CircleView

重写 onMeasure()

计算出⾃自⼰己的尺⼨寸

⽤用 resolveSize() 或者 resolveSizeAndState() 修正结果
3.1resolveSize() / resolveSizeAndState() 内部实现(⼀一定读⼀一下代码,这个极少需要⾃自 ⼰己写,但⾯面试时很多时候会考):
3.1.1⾸首先⽤用 MeasureSpec.getMode(measureSpec) 和 MeasureSpec.getSize(measureSpec) 取出⽗父 对⾃自⼰己的尺⼨寸限制类型和具体限制 尺⼨寸;
3.1.2如果 measure spec 的 mode 是 EXACTLY,表示⽗父 View 对⼦子 View 的尺⼨寸做出 了了精确限制,所以就放弃计算出的 size,直接选⽤用 measure spec 的 size;
3.1.3如果 measure spec 的 mode 是 AT_MOST,表示⽗父 View 对⼦子 View 的尺⼨寸只限 制了了上限,需要看情况:
3.1.4.1如果计算出的 size 不不⼤大于 spec 中限制的 size,表示尺⼨寸没有超出限制, 所以选⽤用计算出的 size;
3.1.4.2而如果计算出的 size ⼤大于 spec 中限制的 size,表示尺⼨寸超限了了,所以选⽤用 spec 的 size,并且在 resolveSizeAndState() 中会添加标志 MEASURED_STATE_TOO_SMALL(这个标志可以辅助⽗父 View 做测量量和布 局的计算;
3.1.5如果 measure spec 的 mode 是 UNSPECIFIED,表示⽗父 View 对⼦子 View 没有任 何尺⼨寸限制,所以直接选⽤用计算出的 size,忽略略 spec 中的 size。

使⽤用 setMeasuredDimension(width, height) 保存结果

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     int width = (int) ((PADDING + RADIUS) * 2);
     int height = (int) ((PADDING + RADIUS) * 2);
     setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec,
 0),
 }

⾃自定义 Layout:重写 onMeasure() 和 onLayout():TagLayout
1. 重写 onMeasure()
1. 遍历每个⼦子 View,⽤用 measureChildWidthMargins() 测量量⼦子 View
• 需要重写 generateLayoutParams() 并返回 MarginLayoutParams 才能使⽤用 measureChildWithMargins() ⽅方法
• 有些⼦子 View 可能需要重新测量量(⽐比如换⾏行行处)
• 测量量完成后,得出⼦子 View 的实际位置和尺⼨寸,并暂时保存

            protected void onMeasure(int widthMeasureSpec, int
            heightMeasureSpec) {
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    Rect childBounds = childrenBounds[i];
                    // 测量量⼦子 View
                    measureChildWithMargins(child, widthMeasureSpec,
            widthUsed,
                    heightMeasureSpec, heightUsed); // 保存⼦子 View 的位置和尺⼨寸
                    childBounds.set(childlLeft, childTop, childLeft
                        + child.getMeasuredWidth(), chiltTop
                        + child.getMeasuredHeight());
                    ...... 
                }
                // 计算⾃自⼰己的尺⼨寸,并保存
                int width = ...;
                int height = ...; setMeasuredDimension(resolveSizeAndState(width,
            widthMeasureSpec, 0),
                        resolveSizeAndState(height,  heightMeasureSpec,
            0)); }
            
        • measureChildWidthMargins() 的内部实现(⼀一定读⼀一下代码,这个极少需要⾃自 ⼰己写,但⾯面试时很多时候会考):
            通过 getChildMeasureSpec(int spec, int padding, int childDimension) ⽅方 法计算出⼦子 View 的 widthMeasureSpec 和 heightMeasureSpec,然后调 ⽤用 child.measure() ⽅方法来让⼦子 View ⾃自我测量量;
        
                    // ViewGroup.measureChildWithMargins() 源码 protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
                            int parentHeightMeasureSpec, int heightUsed) {
                        final MarginLayoutParams lp =
                                (MarginLayoutParams) child.getLayoutParams();
                        final int childWidthMeasureSpec =
                     
                    getChildMeasureSpec(int spec, int padding, int childDimension) ⽅方法的内部 实现是,结合开发者设置的 LayoutParams 中的 width 和 height 与⽗父 View ⾃自 ⼰己的剩余可⽤用空间,综合得出⼦子 View 的尺⼨寸限制,并使⽤用 MeasureSpec.makeMeasureSpec(size, mode) 来求得结果:
                    getChildMeasureSpec(parentWidthMeasureSpec,
                                mPaddingLeft + mPaddingRight + lp.leftMargin
                                + lp.rightMargin + widthUsed, lp.width);
                        final int childHeightMeasureSpec =
                                getChildMeasureSpec(parentHeightMeasureSpec,
                                mPaddingTop + mPaddingBottom + lp.topMargin
                                + lp.bottomMargin + heightUsed, lp.height);
                        child.measure(childWidthMeasureSpec,
                    childHeightMeasureSpec);
                    }
                    
                getChildMeasureSpec(int spec, int padding, int childDimension) ⽅方法的内部 实现是,结合开发者设置的 LayoutParams 中的 width 和 height 与⽗父 View ⾃自 ⼰己的剩余可⽤用空间,综合得出⼦子 View 的尺⼨寸限制,并使⽤用 MeasureSpec.makeMeasureSpec(size, mode) 来求得结果:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值