自定义view之layout()与onLayout()方法

本文详细解析了Android自定义View中layout()与onLayout()两个关键方法。layout()方法用于设置View的位置,更新mLeft, mTop, mRight, mBottom并触发重绘;onLayout()则是ViewGroup对子View布局的抽象方法,必须在ViewGroup中实现,用于安排子View的位置。requestLayout()则用于在需要时重新布局。" 116594208,10545855,Linux 网络丢包问题排查与解决,"['Linux', '网络监控', '系统诊断', '网络维护']

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

layout()与onLayout()

layout()与onLayout()两个方法是在view的layout(布置)过程中最重要的两个方法。onLayout()在layout()方法内部被调用。首先看看layout()方法的实现:

layout()方法的实现

public void layout(int l, int t, int r, int b) {
    //确保测量工作已经完成
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }

    //保存旧的位置坐标
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;

    //重点步骤,changed表示该view的尺寸或位置是否发生了改变
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

    //如果changed为true或是需要重新布置(比如measure过程结束)
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        //调用onLayout()方法
        onLayout(changed, l, t, r, b);
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

        //调用onLayoutChange()回调方法
        onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);

    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
    mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

方法比较长,因此去掉了一些和本文关系不大的内容。每块代码都给出了说明。下面重点看看这一句:

boolean changed = isLayoutModeOptical(mParent) ?
        setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

事实上,无论调用的是setOpticalFrame(l, t, r, b)还是setFrame(l, t, r, b),最终都会进入下面的方法中:

protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    //如果left,right,top,bottom有一个发生了变化,则重新开始布置。实质上是判断view的位置或尺寸有没有发生变化。
    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        //设置changed为true
        changed = true;

        //计算旧的宽高
        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        //计算新的宽高
        int newWidth = right - left;
        int newHeight = bottom - top;
        //判断尺寸是否发生了变化
        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

        //重绘旧位置
        invalidate(sizeChanged);

        //更新位置坐标
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;

        //最终会调用onSizeChanged()回调方法
        if (sizeChanged) {
            sizeChange(newWidth, newHeight, oldWidth, oldHeight);
        }

        //重绘新位置
        invalidate(sizeChanged);

    }
    return changed;
}

可以看到,该方法是通过更新了mLeft,mTop,mRight,mBottom,并对之前和之后的位置进行重绘来达到改变view位置的目的。事实上,在该方法调用后,getWidth()与getHeight()才能返回真实的宽高。原因如下:

public final int getWidth() {
    return mRight - mLeft;
}
public final int getHeight() {
    return mBottom - mTop;
}

这里顺便说明一下它们与getMeasuredWidth(),getMeasuredHeight()的区别。从名字上也能看出来,这两个返回的是measure过程中得到的宽高。我们知道,measure过程是先于layout过程发生的。如果我们在调用layout(int l, int t, int r, int b)的过程中,让r-l == getMeasuredWidth(),b-t == getMeasuredHeight(),那么两组方法的返回值就完全相等,否则就会产生区别。
layout()方法的内容差不多就这些了,总结一下就是:调用layout(int l, int t, int r, int b),会将view放置到左上角坐标(l,t),右下角坐标(r,b)的位置上(注意这里的坐标都是相对于父view而言的,不是绝对坐标),并调用onLayout(boolean changed,int l, int t, int r, int b)方法。
下面看看onLayout()方法:

onLayout()方法的实现

//View类中的onLayout()方法
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

可以看到,View类中的onLayout()方法是个空方法。这也是在暗示我们,对于View而言,不需要去实现onLayout()方法。接下到ViewGroup里去看看:

//ViewGroup中的onLayout()方法
protected abstract void onLayout(boolean changed,
        int l, int t, int r, int b);

ViewGroup中的onLayout()是一个abstract的方法。这意味着,如果要实现一个ViewGroup,就必须实现它的onLayout()方法。到这里结论已经很明显了:onLayout()方法是用来对子View进行layout的。一般来说,我们需要做的就是根据上一步measure的结果,结合我们想要实现的ViewGroup的特点,对所有子view调用layout()方法,将它们放置到合适的位置上。

requestLayout()

在某些情况下,可能会需要重新对View进行layout(比如添加了新的子view时),此时可以使用requestLayout()方法,调用它会重新执行该view的measure以及layout()方法。简单看一下它的实现:

public void requestLayout() {
    //标记当前view,表示该view需要重新进行layout
    mPrivateFlags |= PFLAG_FORCE_LAYOUT;
    mPrivateFlags |= PFLAG_INVALIDATED;

    //调用父view的requestLayout()方法
    if (mParent != null && !mParent.isLayoutRequested()) {
        mParent.requestLayout();
    }
}

只保留了重点部分。实质上就是先给自己打上需要layout的标记,再调用父view的该方法。这样一步步向上提交,最终会到达ViewRootImpl处。ViewRootImpl会对每一个打上标记的view及其子View重新进行measure,layout,draw。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值