自定義view中onMeasure、onLayout、onDraw、onFinishInflate、onSizeChanged方法呼叫時機
阿新 • • 發佈:2019-02-15
一般自定義view或viewGroup基本上都會去實現onMeasure、onLayout、onDraw方法,還有另外兩個方法是onFinishInflate和onSizeChanged。
onFinishInflate方法只有在佈局檔案中載入view例項會回撥,如果直接new一個view的話是不會回撥的。
比如一個ViewGroup,只有它和它的子view完全被載入例項化了之後才回去回撥該viewGroup的這個方法。因為在LayoutInflater的inflate執行過程中最終的呼叫路徑是:inflate --》 rInflateChildren --》 rInflate , 其中inflate的呼叫是這樣的:
完成後,會有個判斷
所以我們的onMeasure及後續的方法應該是在onFinishInflate之後呼叫的。
而onSizeChanged方法一般是檢視大小發生變化的時候回調了,那麼具體看原始碼是在layout的過程中出發的,在layout方法中會呼叫setFrame方法,在setFrame方法中又呼叫了sizeChange,在該方法裡面回調了onSizeChanged,然後才回去回撥onLayout過程。
setFrame過程:
onFinishInflate方法只有在佈局檔案中載入view例項會回撥,如果直接new一個view的話是不會回撥的。
比如一個ViewGroup,只有它和它的子view完全被載入例項化了之後才回去回撥該viewGroup的這個方法。因為在LayoutInflater的inflate執行過程中最終的呼叫路徑是:inflate --》 rInflateChildren --》 rInflate , 其中inflate的呼叫是這樣的:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized然後,通過遞迴呼叫rInflate解析所有的xml節點,如下:(mConstructorArgs) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; View result = root; try{ // Look for the root node. int type; while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); if (DEBUG) { System.out.println("**************************"); System.out.println("Creating root view: " + name); System.out.println("**************************"); } if (TAG_MERGE.equals(name)) { if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } rInflate(parser, root, inflaterContext, attrs, false); } else { // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { if (DEBUG) { System.out.println("Creating params from root: " + root); } // Create layout params that match root, if supplied params = root.generateLayoutParams(attrs); if (!attachToRoot) { // Set the layout params for temp if we are not // attaching. (If we are, we use addView, below) temp.setLayoutParams(params); } } if (DEBUG) { System.out.println("-----> start inflating children"); } // Inflate all children under temp against its context. rInflateChildren(parser, temp, attrs, true); if (DEBUG) { System.out.println("-----> done inflating children"); } // We are supposed to attach all the views we found (int temp) // to root. Do that now. if (root != null && attachToRoot) { root.addView(temp, params); } // Decide whether to return the root that was passed in or the // top view found in xml. if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { final InflateException ie = new InflateException(e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } catch (Exception e) { final InflateException ie = new InflateException(parser.getPositionDescription() + ": " + e.getMessage(), e); ie.setStackTrace(EMPTY_STACK_TRACE); throw ie; } finally { // Don't retain static reference on context. mConstructorArgs[0] = lastContext; mConstructorArgs[1] = null; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } return result; } }
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } else { final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); } } if (finishInflate) { parent.onFinishInflate(); } }
完成後,會有個判斷
if (finishInflate) {
parent.onFinishInflate();
}
這時候通知父控制元件執行onFinishInflate方法,而此時,也緊緊是將所有的子控制元件例項化到記憶體中,也就是可以通過getChildAt()來獲取相應的子控制元件例項了。
這時候還沒執行onMeasure呢,而通過layoutInflate.inflate出來的view或viewGroup往往是要新增到已有的父控制元件,比如使用setContentView方式獲取我們的xml檔案,其中做了幾個步驟:
首先去xml中解析出我們的佈局view,然後在把這個view新增到activity的頂級檢視中(即DecorView的子佈局content中),新增的過程必然使用到了addView的操作,而該操作就會觸發requestLayout和invalidate這兩個方法,這兩個方法又必然會觸發ViewParent(即ViewRootImpl)這個檢視管理者的一系列操作,這一系列操作由performTraversalse開始,順序去呼叫performMeasure -> view.measure -> onMeasure,performLayout
-> view.layout -> onLayout,performDraw -> draw -> drawSoftWare -> view.draw -> onDraw(實現自身的繪製) -> dispatchDraw (實現子view的繪製,呼叫drawChild)。所以我們的onMeasure及後續的方法應該是在onFinishInflate之後呼叫的。
而onSizeChanged方法一般是檢視大小發生變化的時候回調了,那麼具體看原始碼是在layout的過程中出發的,在layout方法中會呼叫setFrame方法,在setFrame方法中又呼叫了sizeChange,在該方法裡面回調了onSizeChanged,然後才回去回撥onLayout過程。
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;
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
setFrame過程:
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if (DBG) {
Log.d("View", this + " View.setFrame(" + left + "," + top + ","
+ right + "," + bottom + ")");
}
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
changed = true;
// Remember our drawn bit
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
// Invalidate our old position
invalidate(sizeChanged);
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
if (sizeChanged) {
sizeChange(newWidth, newHeight, oldWidth, oldHeight);
}
if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
// If we are visible, force the DRAWN bit to on so that
// this invalidate will go through (at least to our parent).
// This is because someone may have invalidated this view
// before this call to setFrame came in, thereby clearing
// the DRAWN bit.
mPrivateFlags |= PFLAG_DRAWN;
invalidate(sizeChanged);
// parent display list may need to be recreated based on a change in the bounds
// of any child
invalidateParentCaches();
}
// Reset drawn bit to original value (invalidate turns it off)
mPrivateFlags |= drawn;
mBackgroundSizeChanged = true;
if (mForegroundInfo != null) {
mForegroundInfo.mBoundsChanged = true;
}
notifySubtreeAccessibilityStateChangedIfNeeded();
}
return changed;
}