分析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后会导致重绘并且复原。