Android view调用layout移动之后,调用setText又复原的原因

分析setText源码有这么一段代码。

    if (mLayout != null) {
            checkForRelayout();
        }

我们再到checkForRelayout方法中看看,

 /**
     * Check whether entirely new text requires a new view layout
     * or merely a new text layout.
     */
    private void checkForRelayout() {
        // If we have a fixed width, we can just swap in a new text layout
        // if the text height stays the same or if the view height is fixed.

        if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
                || (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
                && (mHint == null || mHintLayout != null)
                && (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
            // Static width, so try making a new text layout.

            int oldht = mLayout.getHeight();
            int want = mLayout.getWidth();
            int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();

            /*
             * No need to bring the text into view, since the size is not
             * changing (unless we do the requestLayout(), in which case it
             * will happen at measure).
             */
            makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
                          mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
                          false);

            if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
                // In a fixed-height view, so use our new text layout.
                if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
                        && mLayoutParams.height != LayoutParams.MATCH_PARENT) {
                    autoSizeText();
                    invalidate();
                    return;
                }

                // Dynamic height, but height has stayed the same,
                // so use our new text layout.
                if (mLayout.getHeight() == oldht
                        && (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
                    autoSizeText();
                    invalidate();
                    return;
                }
            }

            // We lose: the height has changed and we have a dynamic height.
            // Request a new view layout using our new text layout.
            requestLayout();
            invalidate();
        } else {
            // Dynamic width, so we have no choice but to request a new
            // view layout with a new text layout.
            nullLayouts();
            requestLayout();
            invalidate();
        }
    }

可以发现,当不满足if中的条件的时候,会走到下面的重绘方法:
nullLayouts(); requestLayout(); invalidate();

注释写的是:Dynamic width, so we have no choice but to request a new view layout with a new text layout。即因为textView的宽度是动态的导致了重绘。

第一个if条件 (mLayoutParams.width !=LayoutParams.WRAP_CONTENT|| (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))。
即判断textView是否设置为wrap_content,同时判断最大宽度和最小宽度的模式和值是否相等。当textView为wrap_content的时候,并且不相等的时候该条件不满足,即textView宽度是动态。

第二个if条件(mHint == null || mHintLayout != null)。
即判断textView是否显示默认提示内容,
当默认提示内容不为空,并且显示默认提示内容的容器mHintLayout 为空的时候该条件不满足。一般在调用setHint方法会触发该条件,同时导致重绘。

第三个if条件(mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)
即判断textView的文字区域宽度是否大于0,
当为0或者小于0的时候导致重绘。

如果if条件满足,但走到如下代码,也会导致重绘。

// We lose: the height has changed and we have a dynamic height.
            // Request a new view layout using our new text layout.
            requestLayout();
            invalidate();

可以看到前面有个if判断,
if (mEllipsize != TextUtils.TruncateAt.MARQUEE)
即当设置为跑马灯模式的时候,会导致重绘。
当没有设置为跑马灯模式的时候,不满足里面的两个if条件,
分别判断高度设置是不是动态的,以及高度是否发生了变化,如果高度设置为动态的高度,并且高度发生了变化,也会导致重绘。

在requestLayout方法中,首先先判断当前View树是否正在布局流程,接着为当前子View设置标记位,该标记位的作用就是标记了当前的View是需要进行重新布局的,接着调用mParent.requestLayout方法,即调用父容器的requestLayout方法,为父容器添加PFLAG_FORCE_LAYOUT标记位,而父容器又会调用它的父容器的requestLayout方法,即requestLayout事件层层向上传递,直到DecorView,即根View,而根View又会传递给ViewRootImpl,也即是说子View的requestLayout事件,最终会被ViewRootImpl接收并得到处理。纵观这个向上传递的流程,其实是采用了责任链模式,即不断向上传递该事件,直到找到能处理该事件的上级,最后到ViewRootImpl能够处理requestLayout事件,
在ViewRootImpl中调用了scheduleTraversals方法,在这个方法内部,分别调用measure、layout、draw方法来进行View的三大工作流程。

在measure方法中:

 // Optimize layout by avoiding an extra EXACTLY pass when the view is
        // already measured as the correct size. In API 23 and below, this
        // extra pass is required to make LinearLayout re-distribute weight.
        final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
                || heightMeasureSpec != mOldHeightMeasureSpec;
        final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
                && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
        final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
                && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
        final boolean needsLayout = specChanged
                && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);

会根据widthMeasureSpec和heightMeasureSpec 判断MeasureSpec发生了变化,同时根据MeasureSpec的模式,判断当前是否有必要重新测量。

因为前面view调用了layout移动,所以调用setText后会导致重绘并且复原。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值