自定义View之重写onMeasure

简介: 自定义View之重写onMeasure

一、重写onMeasure()来修改已有的View的尺寸

步骤

  1. 重写 onMeasure(),并调用 super.onMeasure() 触发原先的测量
  2. 用 getMeasuredWidth() 和 getMeasuredHeight() 取到之前测得的尺寸,利用这两个尺寸来计算出最终尺寸
  3. 使用 setMeasuredDimension() 保存尺寸

代码:

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       //先执行原测量算法
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取原先的测量结果
        int measureWidth=getMeasuredWidth();
        int measureHeight=getMeasuredHeight();
        //利用原先的测量结果计算出新的尺寸
        if(measureWidth>measureHeight){
            measureWidth=measureHeight;
        }else{
            measureHeight=measureWidth;
        }
        //保存计算后的结果
        setMeasuredDimension(measureWidth,measureHeight);
    }

二、重写onMeasure()来全新计算自定义View的尺寸

步骤:

  1. 重写 onMeasure0) 把尺寸计算出来
  2. 把计算的结果用 resolveSize() 过滤一遍后保存
@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        measuredWidth=...;
        measuredHeight=...;
        
        measuredWidth=resolveSize(measuredWidth,widthMeasureSpec);
        measuredHeight=resolveSize(measuredHeight,heightMeasureSpec);
        
        setMeasuredDimension(measuredWidth,measuredHeight);
    }

      onMeasure()方法的两个参数 widthMeasureSpec和heightMeasureSpec是父View对子View的尺寸限制,子View在计算自己尺寸的时候,需要遵守这两个参数所包含的限制MeasureSpec。

理解MeasureSpec

在 Android 中,View 的大小是由父容器和 View 自身的测量规格(MeasureSpec)共同决定的。

MeasureSpec 由大小和测量模式组成,测量模式有三种取值:

  1. UNSPECIFIED(未指定):父容器对子 View 没有施加任何限制,子 View 可以任意大小。
  2. EXACTLY(精确):父容器已经为子 View 精确指定了大小,子 View 应该匹配这个大小。
  3. AT_MOST(至多):子 View 可以是任何大小,但不能超过父容器指定的大小。

MeasureSpec 是通过静态方法 MeasureSpec.makeMeasureSpec() 创建的,该方法接受两个参数:大小和测量模式。在自定义 View 或者自定义布局中,我们通常会使用 MeasureSpec 来测量子 View 的大小,并根据测量模式来决定子 View 的大小。

在自定义 View 中,我们通常会在 onMeasure() 方法中使用 MeasureSpec 来测量 View 的大小。在这个方法中,我们可以通过 MeasureSpec.getMode() 和 MeasureSpec.getSize() 方法来获取测量模式和大小,然后根据这些信息来确定 View 的最终大小。

解释resolveSize()这个方法:

//代码简化,不是源码
public static int resolveSize(int size, int measureSpec) {
      final int specMode = MeasureSpec.getMode(measureSpec);
        final int specSize = MeasureSpec.getSize(measureSpec);
         switch (specMode) {
            case MeasureSpec.AT_MOST:
                if (specSize < size) {
                    result = specSize | MEASURED_STATE_TOO_SMALL;
                } else {
                    result = size;
                }
                break;
            case MeasureSpec.EXACTLY:
                result = specSize;
                break;
            case MeasureSpec.UNSPECIFIED:
            default:
                result = size;
        }
}

resolveSize()这个方法,父View传进来的尺寸限制measureSpec是由类型和尺寸值组成的,首先要调用MeasureSpec.getMode(measureSpec)方法和MeasureSpec.getSize(measureSpec)方法获取限制measureSpec的类型mode和size尺寸值。

限制的类型mode:

MeasureSpec.AT_MOST 限制上线

MeasureSpec.EXACTLY 限制固定尺寸

MeasureSpec.UNSPECIFIED 无限制

三、重写onMeasure()和onLayout()来全新计算自定义ViewGroup的内部布局

onMeasure()的重写,对于ViewGroup来说,包含三部分内容:

步骤:

  1. 调用每个子View的measure(),让子View自我测量
  2. 根据子View给出的尺寸,得出子View的位置,并保存它们的位置和尺寸
  3. 根据子View的位置和尺寸计算出自己的尺寸,并用setMeasuredDimension()保存

理解LayoutParams

      在父View里调用子View的getLayoutParams()方法,可以获得一个LayoutParams对象,它包含了xml文件里的layout_打头的参数的对应值,其中它的width和height这两个属性就分别对应了layout_width和layout_height的值,并且是转换过了的值。

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        for(int i=0;i<getChildCount();i++){
            View childView=getChildAt(i);
            LayoutParams lp=childView.getLayoutParams();
            //lp.height   lp.width
        }
    }

结合自己的可用空间来计算出对子View的宽度和高度的限制

可以根据layout_width和layout_height的值,分成三种情况:

第一种情况:固定值

不需要考虑可用空间的问题,直接用EXACTLY把子View尺寸限制为这个固定值就可以了。

第二种情况:match_parent

把子View的尺寸限制为固定值可用宽度或者高度

可用空间的判断方法:

根据自己的MeasureSpec中mode的不同:

1.EXACTLY/AT_MOST   可用空间:MeasureSpec中的size

2.UNSPECIFIED     可用空间:无限大

第三种情况:wrap_content

不能超过父View的边界的情况下,子View自己测量

public class SomeView extends ViewGroup {
...
 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        for(int i=0;i<getChildCount();i++){
            View childView=getChildAt(i);
            LayoutParams lp=childView.getLayoutParams();
            int selfwidthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
            int selfwidthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
            switch (lp.width){
                case MATCH_PARENT:
                    if(selfwidthSpecMode==EXACTLY||selfwidthSpecMode==MeasureSpec.AT_MOST){
                        childWidthSpec=MeasureSpec.makeMeasureSpec(selfwidthSpecSize-usedWidth,EXACTLY);
                    }else{
                        childWidthSpec=MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
                    }
                    break;
                case  WRAP_CONTENT:
                    if(selfwidthSpecMode==EXACTLY||selfwidthSpecMode==MeasureSpec.AT_MOST){
                        childWidthSpec=MeasureSpec.makeMeasureSpec(selfwidthSpecSize-usedWidth,MeasureSpec.AT_MOST);
                    }else{
                        childWidthSpec=MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
                    }
                    break;
                default:
                    childWidthSpec=MeasureSpec.makeMeasureSpec(lp.width, EXACTLY);
                    break;
            }
        }
    }
}

关于保存子View位置的两点说明

1.不是所有的Layout都需要保存子View的位置(因为有的Layout可以在布局阶段实时推导出子View的位置,例如LinearLayout)

2.有时候对某些子View需要重复测量两次或多次才能得到正确的尺寸和位置

重写onLayout()来摆放子View

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for(int i=0;i<getChildCount();i++){
            View childView=getChildAt(i);
            childView.layout(childLeft[i],childTop[i],childRight[i],childBottom[i]);
        }
    }

 

目录
相关文章
|
Android开发
【错误记录】Android 可执行权限报错 ( Cannot run program “/data/user/0/cn.e/ffmpeg“: error=13,Permission denied )
【错误记录】Android 可执行权限报错 ( Cannot run program “/data/user/0/cn.e/ffmpeg“: error=13,Permission denied )
2172 0
【错误记录】Android 可执行权限报错 ( Cannot run program “/data/user/0/cn.e/ffmpeg“: error=13,Permission denied )
|
11月前
|
C# UED SEO
C# 异步方法async / await任务超时处理
通过使用 `Task.WhenAny`和 `Task.Delay`方法,您可以在C#中有效地实现异步任务的超时处理机制。这种方法允许您在指定时间内等待任务完成,并在任务超时时采取适当的措施,如抛出异常或执行备用操作。希望本文提供的详细解释和代码示例能帮助您在实际项目中更好地处理异步任务超时问题,提升应用程序的可靠性和用户体验。
472 3
|
Java Android开发 UED
🧠Android多线程与异步编程实战!告别卡顿,让应用响应如丝般顺滑!🧵
在Android开发中,为应对复杂应用场景和繁重计算任务,多线程与异步编程成为保证UI流畅性的关键。本文将介绍Android中的多线程基础,包括Thread、Handler、Looper、AsyncTask及ExecutorService等,并通过示例代码展示其实用性。AsyncTask适用于简单后台操作,而ExecutorService则能更好地管理复杂并发任务。合理运用这些技术,可显著提升应用性能和用户体验,避免内存泄漏和线程安全问题,确保UI更新顺畅。
373 5
|
机器学习/深度学习 数据可视化 PyTorch
PyTorch小技巧:使用Hook可视化网络层激活(各层输出)
这篇文章将演示如何可视化PyTorch激活层。可视化激活,即模型内各层的输出,对于理解深度神经网络如何处理视觉信息至关重要,这有助于诊断模型行为并激发改进。
494 1
|
安全 网络安全 数据安全/隐私保护
BUUCTF:Misc 解析(十)
BUUCTF:Misc 解析(十)
|
Java 机器人 Maven
申请发布maven中央仓库的账号
申请发布maven中央仓库的账号
271 0
Java 中表示整数的包装类Integer(详解)
Java 中表示整数的包装类Integer(详解)
|
弹性计算 并行计算 UED
带你读《弹性计算技术指导及场景应用》——4. 自动安装NVIDIA GPU驱动和CUDA组件
带你读《弹性计算技术指导及场景应用》——4. 自动安装NVIDIA GPU驱动和CUDA组件
285 0
|
XML Android开发 数据格式
Android XML 布局基础(一)布局页面使用
Android XML 布局基础(一)布局页面使用
562 0
|
XML Android开发 数据格式
Android Studio进行APP设计开发之矢量图及XML文件转换
Android Studio进行APP设计开发之矢量图及XML文件转换
1469 0
Android Studio进行APP设计开发之矢量图及XML文件转换