Android学習ノート15:AndroidでのViewペイントの流れについての理解

16121 ワード

Viewツリー全体の描画プロセスはView Rootです.JAvaクラスのperformTraversals()メソッドが展開され、この関数の実行手順は、以前に設定した状態に基づいて、ビューサイズの再計算が必要かどうか、ビューの位置の再配置が必要かどうか、および再描画が必要かどうかを簡単に概説することができる.
フロー1:mesarue()測定プロセス
主な役割:ビューツリー全体の実際のサイズを計算します.つまり、実際の高さ(対応するプロパティ:mMeasuredHeight)と幅(対応するプロパティ:mMeasureWidth)を設定します.各ビューコントロールの実際の高さと幅は、親ビューと自分のビューで決まります.
具体的な呼び出しプロセス:ViewRootのプロパティmView(一般的にはViewGroupタイプ)はmeasure()メソッドを呼び出してViewツリーのサイズを計算し、View/ViewGroupオブジェクトのonMeasure()メソッドをコールバックします.このメソッドは次のように機能します.
1、本ビューの最終サイズを設定し、この機能の実現はsetMeasuredDimension()メソッドを呼び出して実際の高さ(対応属性:mMeasuredHeight)と幅(対応属性:mMeasureWidth)を設定する.
2.ビューオブジェクトがViewGroupタイプの場合、onMeasure()メソッドを書き換え、サブビューを巡回するmeasure()プロシージャが必要です.各サブビューに対するmeasure()プロシージャは、親のViewGroupを呼び出す.JAvaクラスのmeasureChildWithMargins()メソッドは実装され,このメソッドの内部ではViewオブジェクトのmeasure()メソッドを簡単に呼び出すだけである.(measureChildWithMargins()メソッドは遷移にすぎないため、より簡単な方法はViewオブジェクトのmeasure()メソッドを直接呼び出すことです).
measrue()全体の呼び出しプロセスは、ツリーの再帰プロセスです.
measure()メソッドのソースコード:
This is called to find out how big a view should be. The parent supplies constraint information in the width and height parameters.
The actual mesurement work of a view is performed in onMeasure(int,int), called by this method. Therefore, only onMeasure(int,int) can and must be 

overriden by subclasses.

Parameters:
	widthMeasureSpec Horizontal space requirements as imposed by the parent
	heightMeasureSpec Vertical space requirements as imposed by the parent
See also:
	onMeasure(int,int)

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
	if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
		widthMeasureSpec != mOldWidthMeasureSpec ||
		heightMeasureSpec != mOldHeightMeasureSpec) {

		// first clears the measured dimension flag
		mPrivateFlags &= ~MEASURED_DIMENSION_SET;
	
		if (ViewDebug.TRACE_HIERARCHY) {
			ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
		}

		// measure ourselves, this should set the measured dimension flag back
		onMeasure(widthMeasureSpec, heightMeasureSpec);

		// flag not set, setMeasuredDimension() was not invoked, we raise
		// an exception to warn the developer
		if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
			throw new IllegalStateException("onMeasure() did not set the"
				+ " measured dimension by calling"
				+ " setMeasuredDimension()");
		}

		mPrivateFlags |= LAYOUT_REQUIRED;
	}

	mOldWidthMeasureSpec = widthMeasureSpec;
	mOldHeightMeasureSpec = heightMeasureSpec;
}

皆さんがよりよく理解するために、「二Bプログラマー」方式で偽コードを利用してこのmeasureの流れを説明します.
// ViewRoot.java
// measure()    
//   measure()   "   "  ViewRoot.java  performTraversals()    mView.measure()
private void performTraversals() {
	
	//... 
	View mView;
	mView.measure(h,l);
	//....

}

//   View    onMeasure  
private void onMeasure(int height , int width) {
	//   view    (mMeasuredHeight)  (mMeasuredWidth) 
	//1、      onMeasure  ,     。
	setMeasuredDimension(h , l);

	//2、   View ViewGroup  ,       View  measure()  
	int childCount = getChildCount();

	for(int i = 0; i < childCount; i++){
		//2.1、     View    
		View child = getChildAt(i);

		//  measure()         
		//          ,     measure()    measureChild(child , h, i)  
		measureChildWithMargins(child , h, i) ; 

		//  ,            ,               ,    view.measure(),  :
		//child.measure(h, l);
	}
}

//     ViewGroup.java     
protected void measureChildWithMargins(View v, int height , int width) {
	v.measure(h,l);
}

プロセス2:layout()レイアウトプロセス
主な役割:ビューのサイズとレイアウトパラメータに基づいて、ビューツリーを適切な位置に配置します.
具体的な呼び出し手順:host.Layout()は、Viewツリーのレイアウトを開始し、次にView/View Groupクラスのlayout()メソッドにコールバックします.具体的な手順は、次のとおりです.
1、layout()メソッドは、親ビューの座標軸、すなわちmLeft、mTop、mLeft、mBottom(setFrame()関数を呼び出して実装する)にビューを設定し、次にonLayout()メソッド(ビューがViewGroupオブジェクトである場合、このメソッドを実装する必要があり、各サブビューをレイアウトする)にコールバックする.
2.ViewがView Groupタイプの場合、各サブビューchildViewを巡回し、サブビューのlayout()メソッドを呼び出して座標値を設定する必要があります.
Layout()メソッドのソースコード:
Assign a size and position to a view and all of its descendants
This is the second phase of the layout mechanism. (The first is measuring). In this phase, each parent calls layout on all of its children to position them. 

This is typically done using the child measurements that were stored in the measure pass().
Derived classes should not override this method. Derived classes with children should override onLayout. In that method, they should call layout on each 

of their children.

Parameters:
	l Left position, relative to parent
	t Top position, relative to parent
	r Right position, relative to parent
	b Bottom position, relative to parent

@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
	int oldL = mLeft;
	int oldT = mTop;
	int oldB = mBottom;
	int oldR = mRight;
	boolean changed = setFrame(l, t, r, b);	//                
	if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
		if (ViewDebug.TRACE_HIERARCHY) {
			ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
		}

		onLayout(changed, l, t, r, b);	//  onLayout()  ,            
		mPrivateFlags &= ~LAYOUT_REQUIRED;

		ListenerInfo li = mListenerInfo;
		if (li != null && li.mOnLayoutChangeListeners != null) {
			ArrayList listenersCopy =
				(ArrayList)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 &= ~FORCE_LAYOUT;
}

同様に、上述したlayout()呼び出しプロセスを擬似コードで以下に説明する.
// ViewRoot.java
// layout()  
//   layout()   “   ”  ViewRoot.java  performTraversals()    mView.layout()  
private void  performTraversals(){

	//...
	View mView;
	mView.layout(left, top, right, bottom);
	//....
}

//   View    onLayout  ,     ViewGroup    
private void onLayout(int left, int top, right, bottom) {

	//    View  ViewGroup  
	//   setFrame()                 
	setFrame(l, t, r, b) ;

	//--------------------------

	//    View ViewGroup  ,       View  layout()  
	int childCount = getChildCount();
	for(int i = 0; i < childCount; i++){
		//      View    
		View child = getChildAt(i);
		//   layout()         
		child.layout(l, t, r, b);
	}
}

プロセス3:draw()ペイントプロセス
ViewRootオブジェクトのperformTraversals()メソッドによってdraw()メソッドが呼び出され、そのViewツリーを描画するプロセスが開始されます.図面を描画するたびに、各Viewツリーのビューを再描画するのではなく、再描画が必要なビューのみが再描画されます.Viewクラスの内部変数にはフラグビットDRAWNが含まれています.このビューを再描画する必要がある場合、ビューにフラグビットが追加されます.
具体的な呼び出しプロセス:mView.draw()は描画を開始し、draw()メソッドは以下の機能を実現します.
1、このViewの背景を描く.
2、ランプボックスを表示するためにいくつかの準備操作をする(5を参照して、多くの場合、ランプボックスを変更する必要はありません).
3、onDraw()メソッドを呼び出してビュー自体を描画する(各Viewはこのメソッドを再ロードする必要があり、View Groupはこのメソッドを実装する必要はありません).
4、dispatchDraw()メソッドの内部には各サブビューが遍歴し、drawChild()を呼び出して各サブビューのdraw()メソッドを再コールします(ここで「再描画が必要」なビューがdraw()メソッドを呼び出すことに注意してください).ビュータイプがビューグループでない場合、サブビューが含まれていない場合は、メソッドを再ロードする必要はありません.説明に値するのは、ViewGroupクラスがdispatchDraw()の機能実装を書き換えてくれたことであり、アプリケーションは一般的にこの方法を書き換える必要はありませんが、親関数を再ロードして具体的な機能を実現することができます.
5、スクロールバーを描画します.
すると、呼び出しプロセス全体がこのように再帰されます.
draw()メソッドのソースコード:
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called.  When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/

public void draw(Canvas canvas) {

	if (ViewDebug.TRACE_HIERARCHY) {
		ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);
	}


	final int privateFlags = mPrivateFlags;
	final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&
		(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
	mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;


	/*
	* Draw traversal performs several drawing steps which must be executed
	* in the appropriate order:
	*
	*      1. Draw the background
	*      2. If necessary, save the canvas' layers to prepare for fading
	*      3. Draw view's content
	*      4. Draw children
	*      5. If necessary, draw the fading edges and restore layers
	*      6. Draw decorations (scrollbars for instance)
	*/

	// Step 1, draw the background, if needed
	int saveCount;
	if (!dirtyOpaque) {
		final Drawable background = mBGDrawable;
		if (background != null) {
			final int scrollX = mScrollX;
			final int scrollY = mScrollY;
			
			if (mBackgroundSizeChanged) {
				background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
				mBackgroundSizeChanged = false;
			}

			if ((scrollX | scrollY) == 0) {
				background.draw(canvas);
			} else {
				canvas.translate(scrollX, scrollY);
				background.draw(canvas);
				canvas.translate(-scrollX, -scrollY);
			}
		}
	}

	// skip step 2 & 5 if possible (common case)
	final int viewFlags = mViewFlags;
	boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
	boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
	if (!verticalEdges && !horizontalEdges) {

		// Step 3, draw the content
		if (!dirtyOpaque) onDraw(canvas);

		// Step 4, draw the children
		dispatchDraw(canvas);

		// Step 6, draw decorations (scrollbars)
		onDrawScrollBars(canvas);

		// we're done...
		return;
	}

	/*
	* Here we do the full fledged routine...
	* (this is an uncommon case where speed matters less,
	* this is why we repeat some of the tests that have been
	* done above)
	*/
	
	boolean drawTop = false;
	boolean drawBottom = false;
	boolean drawLeft = false;
	boolean drawRight = false;
	
	float topFadeStrength = 0.0f;
	float bottomFadeStrength = 0.0f;
	float leftFadeStrength = 0.0f;
	float rightFadeStrength = 0.0f;

	// Step 2, save the canvas' layers
	int paddingLeft = mPaddingLeft;
	final boolean offsetRequired = isPaddingOffsetRequired();
	if (offsetRequired) {
		paddingLeft += getLeftPaddingOffset();
	}
	
	int left = mScrollX + paddingLeft;
	int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
	int top = mScrollY + getFadeTop(offsetRequired);
	int bottom = top + getFadeHeight(offsetRequired);

	if (offsetRequired) {
		right += getRightPaddingOffset();
		bottom += getBottomPaddingOffset();
	}
	
	final ScrollabilityCache scrollabilityCache = mScrollCache;
	final float fadeHeight = scrollabilityCache.fadingEdgeLength;
	int length = (int) fadeHeight;

	// clip the fade length if top and bottom fades overlap
	// overlapping fades produce odd-looking artifacts
	if (verticalEdges && (top + length > bottom - length)) {
		length = (bottom - top) / 2;
	}

	// also clip horizontal fades if necessary
	if (horizontalEdges && (left + length > right - length)) {
		length = (right - left) / 2;
	}

	if (verticalEdges) {
		topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
		drawTop = topFadeStrength * fadeHeight > 1.0f;
		bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
		drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
	}

	if (horizontalEdges) {
		leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
		drawLeft = leftFadeStrength * fadeHeight > 1.0f;
		rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
		drawRight = rightFadeStrength * fadeHeight > 1.0f;
	}

	saveCount = canvas.getSaveCount();

	int solidColor = getSolidColor();
	if (solidColor == 0) {
		final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

		if (drawTop) {
			canvas.saveLayer(left, top, right, top + length, null, flags);
		}

		if (drawBottom) {
			canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
		}

		if (drawLeft) {
			canvas.saveLayer(left, top, left + length, bottom, null, flags);
		}

		if (drawRight) {
			canvas.saveLayer(right - length, top, right, bottom, null, flags);
		}
	} else {
		scrollabilityCache.setFadeColor(solidColor);
	}
	
	// Step 3, draw the content
	if (!dirtyOpaque) onDraw(canvas);

	// Step 4, draw the children
	dispatchDraw(canvas);

	// Step 5, draw the fade effect and restore layers
	final Paint p = scrollabilityCache.paint;
	final Matrix matrix = scrollabilityCache.matrix;
	final Shader fade = scrollabilityCache.shader;

	if (drawTop) {
		matrix.setScale(1, fadeHeight * topFadeStrength);
		matrix.postTranslate(left, top);
		fade.setLocalMatrix(matrix);
		canvas.drawRect(left, top, right, top + length, p);
	}

	if (drawBottom) {
		matrix.setScale(1, fadeHeight * bottomFadeStrength);
		matrix.postRotate(180);
		matrix.postTranslate(left, bottom);
		fade.setLocalMatrix(matrix);
		canvas.drawRect(left, bottom - length, right, bottom, p);
	}

	if (drawLeft) {
		matrix.setScale(1, fadeHeight * leftFadeStrength);
		matrix.postRotate(-90);
		matrix.postTranslate(left, top);
		fade.setLocalMatrix(matrix);
		canvas.drawRect(left, top, left + length, bottom, p);
	}

	if (drawRight) {
		matrix.setScale(1, fadeHeight * rightFadeStrength);
		matrix.postRotate(90);
		matrix.postTranslate(right, top);
		fade.setLocalMatrix(matrix);
		canvas.drawRect(right - length, top, right, bottom, p);
	}

	canvas.restoreToCount(saveCount);

	// Step 6, draw decorations (scrollbars)
	onDrawScrollBars(canvas);
}

同様に、上記draw()呼び出しフローを擬似コードで以下に説明する.
// ViewRoot.java
// draw()  
//   draw() “   ”  ViewRoot.java  performTraversals()  ,        draw()        
private void  draw(){
	
	//...
	View mView;
	mView.draw(canvas);
	//....

}

//   View    onDraw()  ,     ViewGroup    
private void draw(Canvas canvas){
	//          
	// 1、   View   
	// 2、             
	// 3、  onDraw()        
	// 4、  dispatchDraw()         ,dispatchDraw()   Android      , ViewGroup   
	// 	                ,           ,        
	//5、         
}

// ViewGroup.java  dispatchDraw()  ,                
@Override  
protected void dispatchDraw(Canvas canvas) {
	
	//
	//          :
	int childCount = getChildCount();
	for(int i = 0; i < childCount; i++) {
		View child = getChildAt(i);
		//  drawChild  
		drawChild(child,canvas);
	}
}

// ViewGroup.java  dispatchDraw()  ,                
protected void drawChild(View child,Canvas canvas) {

	// ....
	//      View   draw()  ,        
	child.draw(canvas);
	//.........  
}

強調しなければならないのは、この3つのプロセスの中で、Googleはdraw()プロセスのフレームワークを書いてくれました.カスタムのView Groupはmeasure()プロセスとlayout()プロセスを実現するだけでいいです.
ビューツリーの再描画を引き起こす要因は次のとおりです.
1、ビューサイズの変化を招く;
2、View Groupが再びサブビューに位置を割り当てる
3、表示状況が変化した場合、再描画が必要
この3つの場合、最終的にはinvalidate()、requsetLaytout()およびrequestFocus()の3つのメソッドが直接または間接的に呼び出され、その後、この3つのメソッドは最終的にView RootのschedulTransversale()メソッドに呼び出され、この関数は非同期メッセージを開始し、メッセージ処理でperformTransverser()メソッドを呼び出してView全体を遍歴する.
invalidate()メソッド:
説明:ビューサイズが変更されていなければlayout()プロシージャは呼び出されず、「再描画が必要な」ビュー、すなわち誰(ビュー)を描画するか、ビューのみが描画されます.ビューグループ全体がinvalidate()メソッドを描画すると、ビューが描画されます.
一般的にinvalidate()操作を引き起こす関数は次のとおりです.
1、invalidate()メソッドを呼び出し、再draw()を要求しますが、呼び出し元自体のみが描画されます.
2、setSelection()メソッドを呼び出し、再draw()を要求しますが、呼び出し元自体のみが描画されます.
3、setVisibility()メソッドを呼び出す:Viewビジュアル状態がINVISIBLEでVISIBLEを変換すると、invalidate()メソッドが間接的に呼び出され、そのViewが描画される.
4、setEnabled()メソッドを呼び出す:再draw()を要求するが、呼び出し元自体を含むビューを再描画しない.
requestLayout()メソッド:
measure()プロシージャとlayout()プロシージャが呼び出されます.
説明:ビューツリーのlayoutプロシージャの再レイアウトにはmeasure()プロシージャとlayout()プロシージャが含まれているだけで、draw()プロシージャは呼び出されませんが、呼び出し元自体を含むビューは再描画されません.
一般的にinvalidate()操作を引き起こす関数は次のとおりです.
1、requestLayout()メソッド:ビューのビジュアルステータスがINVISIBLE/VISIBLEでGONEステータスに変換されると、requestLayout()およびinvalidateメソッドが間接的に呼び出されます.同時に、ビューツリー全体のサイズが変更されたため、measure()プロシージャおよびdraw()プロシージャが要求され、同様に「再描画」が必要なビューのみが描画されます.
requestFocus()メソッド:
説明:ビューツリーのdraw()プロシージャを要求しますが、再描画が必要なビューのみを描画します.
参考資料:
1、AndroidにおけるView描画プロセス及びinvalidate()等の関連方法の分析http://blog.csdn.net/qinjuning/article/details/7110211