Android onMeasure,onLayout,onDraw的理解

本文深入解析自定义View的三个核心函数:onMeasure用于确定View大小;onLayout负责View的位置安排;onDraw则处理View的绘制过程。通过实例代码,帮助读者理解每个函数的工作原理及应用场景。

我们在自定义控件时,有时都需要重写,这三个函数,那这三个函数到底的作用是什么呢?

测量——onMeasure():决定View的大小
布局——onLayout():决定View在ViewGroup中的位置
绘制——onDraw():如何绘制这个View。

View视图可以是单一的一个如TextView,也可以是一个视图组(ViewGroup)如LinearLayout。
如图:对于多View的视图他的结构是树形结构,最顶层是ViewGroup,ViewGroup下可能有多个ViewGroup或View。

这里写图片描述

这个树的概念很重要,因为无论我们是在测量大小或是调整布局的时候都是从树的顶端开始一层一层,一个分支一个分支的进行(树形递归)。measure的作用就是为整个View树计算实际的大小,而通过刚才对View树的介绍知道,想计算整个View树的大小,就需要递归的去计算每一个子视图的大小(onLayout,onDraw同理)。还有就是,在测量之前首先要明确一点,需要测量的是一个View(例如TextView),还是一个ViewGroup(例如LinearLayout),还是多个ViewGroup嵌套。如果只有一个View的话我们就测量这一个就可以了,如果有多个View或者ViewGroup嵌套我们就需要循环遍历视图中所有的View(但这些源码已经替我们完成了(更深的了解,可以自己去看源码,代码简单很好理解的),即使在这种情况下:onMeasure(onDraw,onLayout)也只会执行一次)。特别注意一点,有些朋友调试时,会问为什么onMeasure(onDraw,onLayout)会执行多次,有以下可能性:

你调用了addView等函数,因为你看源码会发现,它们最终会调用:invalidate()函数,说白了,就是重绘这个控件。

下面可以通过代码加深对这几个函数的理解:

public class CustomeTextview extends TextView{
public CustomeTextview(Context context, AttributeSet attrs) {
super(context, attrs);
}

//控件的尺寸获取
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    //该控件的实际宽度
    int width=MeasureSpec.getSize(widthMeasureSpec);
    System.out.println("====width====="+width);

    //获取的这个宽度,对应的精度模式:精确的(EXACTLY)
    int widthMode=MeasureSpec.getMode(widthMeasureSpec);
    /**
     * 如果宽或者高的测量模式非精确值
     */
    if (widthMode != MeasureSpec.EXACTLY) {
        // getSuggestedMinimumWidth :返回该view能使用的最小高度
        width = getSuggestedMinimumWidth();
        System.out.println("====minWidth====="+width);
    }
}

//控件的位置获取
/**
 * 当这个view和其子view被分配一个大小和位置时,被layout调用。
 * @param changed 当前View的大小和位置改变了
 * @param left 左部位置(相对于父视图)
 * @param top 顶部位置(相对于父视图)
 * @param right 右部位置(相对于父视图)
 * @param bottom 底部位置(相对于父视图)
 */
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    System.out.println("====left====="+left);
    System.out.println("====top====="+top);
    System.out.println("====right====="+right);
    System.out.println("====bottom====="+bottom);
}

//如何绘制这个view
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}

}

### 父 View 仅调用 onLayout 的情况 - **手动调用**:开发者可在代码中手动调用父 View 的 onLayout 方法,而不触发 onMeasureonDraw 方法。例如: ```java // 假设 parentView 是一个 ViewGroup 的实例 ViewGroup parentView = findViewById(R.id.parent_view); int left = 0; int top = 0; int right = 100; int bottom = 100; parentView.onLayout(true, left, top, right, bottom); ``` - **异常情况**:若在代码中出现异常,导致测量等流程中断,而刚好满足了调用 onLayout 的条件,可能出现只调用 onLayout 的情况。比如在 onMeasure 方法中抛出异常,后续流程无法正常执行,但由于之前的某些操作触发了 onLayout 的调用条件,就可能只执行 onLayout 方法。 - **布局参数未改变**:当 View 的布局参数没有改变时,系统可能不会重新调用 onMeasure 方法,但可能会因为某些原因调用 onLayout 方法来调整子 View 的位置。例如,父 View 内部的子 View 进行了重新排序,但整体的布局参数没有变化,此时可能只调用 onLayout 方法来重新布局子 View。 - **自定义 ViewGroup 逻辑**:在自定义 ViewGroup 时,如果开发者在代码中手动控制方法的调用,也可能只调用 onLayout 方法。例如,在某些特定的业务逻辑下,开发者认为不需要重新测量,只需要重新布局子 View,就可以直接调用 onLayout 方法。 ### onMeasureonLayout 都调用但 onDraw 未调用的情况 - **View 不可见**:当 View 的 `setVisibility` 方法被设置为 `View.GONE` 时,View 不会进行绘制,因为 `GONE` 表示该 View 完全不可见,且不占用布局空间。此时,虽然会调用 onMeasureonLayout 方法来确定 View 的大小和位置,但不会调用 onDraw 方法。示例代码如下: ```java View parentView = findViewById(R.id.parent_view); parentView.setVisibility(View.GONE); ``` - **缓存机制**:如果开启了 View 的缓存机制,并且缓存的内容仍然有效,系统可能不会再次调用 onDraw 方法。例如,通过 `setDrawingCacheEnabled(true)` 开启了绘制缓存,当布局发生变化时,系统会先检查缓存是否可用,如果可用则直接使用缓存内容,而不会重新调用 onDraw 方法。 ```java View parentView = findViewById(R.id.parent_view); parentView.setDrawingCacheEnabled(true); ``` - **自定义逻辑跳过绘制**:在自定义 View 时,如果开发者在代码中手动控制,在某些条件下跳过 onDraw 方法的执行,也会出现 onMeasureonLayout 都调用但 onDraw 未调用的情况。例如: ```java @Override protected void onDraw(Canvas canvas) { if (shouldSkipDrawing()) { return; } // 正常的绘制逻辑 } private boolean shouldSkipDrawing() { // 根据具体条件判断是否跳过绘制 return false; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值