Viewの描画プロセスの概要
27335 ワード
前のActivityはいつ表示されましたか?Activityの起動とページの表示を解析したが,Viewの描画には深く入り込んでいない.前の分析では、Activityごとに対応するWindowオブジェクトがあり、windowManagerを介していることがわかりました.addView(mDecor, getWindow().getAttributes()はDecorViewへの追加を完了します.最終的にはViewRootImplでmeasure、layout、drawを完了します.
本文はViewの描画の流れをめぐって分析を行って、本文を見終わってあなたは以下のいくつかの問題を理解するかもしれません:1.setMeasuredDimension()はViewのサイズを決定します.2.requestLayout()、invalidate()およびpostInvalidate()リフレッシュコントロールの違い.3.scrollerの使用と原理.
1.1.WindowManagerImpl.addView()
1.2.WindowManagerGlobal.addView()
1.3.ViewRootImpl.setView()-->requestLayout()
1.4.ViewRootImpl.performTraversals()
ここでは、mPrivateFlagsがPFLAG_でない場合、onMeasure()測定コントロールが必要かどうかを判断します.FORCE_LAYOUTでコントロールのサイズが変更されていない場合は、コントロールを再測定する必要はありません.DecorViewはFrameLayoutを継承し、FrameLayoutのonMeasureメソッドを見ているからです.
2.2.FrameLayout.onMeasure()
サブコントロールを巡回することによって、サブコントロールの最も広い幅と最も高い高さを取得し、setMeasuredDimensionメソッドによってFrameLayoutのmMeasuredWidthとmMeasuredHeightを設定します.つまり、コントロールの幅は最終的にsetMeasuredDimension()メソッドで設定されます.
ここではコントロールを複数回layout操作することがわかりますが、同じようにViewのlayoutメソッドを見てみましょう.
ここでは、コントロールの位置が変更されたかどうかを判断し、どちらか一方を満たしてonLayout操作を行う.
巡回サブビューは、サブビューに対応する座標位置をサブビューに伝達する.ViewGroupをカスタマイズするときに再onLayout()メソッドは、義理の子Viewの位置を決めることができます.
最終的にはdrawSoftware()メソッドが実行されます
描画には、背景の描画、それ自体の描画、サブViewの描画、スクロールバーの描画の順にいくつかのステップがあります.
1.1.view.requestLayout()
このことから、requestLayout()メソッドを呼び出すと、mViewにあるmPrivateFlags識別子が再設定されることがわかる.measure()の場合は再測定されます(2.1 mView.measureが見られます)
1.2.viewRootImpl.requestLayout()
最終的には、描画プロセスの1.4が実行する.ViewRootImpl.performTraversals().
2.invalidate()
view.invalidate()-->invalidateInternal()-->viewRootImpl. invalidateRectOnScreen()
ビューのサイズや位置が変更されていない場合は、測定やレイアウトを変更せずにビューを再描画します.
3.postInvalidate()
view.postInvalidate()-->postInvalidateDelayed()-->ViewRootImpl. dispatchInvalidateDelayed()
PostInvalidate()handlerによるMSG_の送信INVALidateメッセージは、最終的に呼び出されるのはinvalidate()メソッドです.
したがって、requestLayout()メソッドはViewツリーの再描画に適しており、invalidate()はViewのサイズと位置が変更されていない場合に描画に適していることをまとめることができます.
scrollerは主にコントロールの滑らかなスライドに用いられ、サブViewは描画時にcomputeScroll()を呼び出し、この方法を書き換えてViewを呼び出す.scrollTo(x,y).これにより、コントロールのスライドが実現されます.では、スムーズなスライドを実現するにはどうすればいいのでしょうか.
ビューで再computeScroll()を実行し、scrollerを使用してスムーズな移動距離を計算します.
2.1スライドの方法
一定の時間内に一定の距離を移動する
2.2移動距離の算出方法
時間が経つにつれて現在スライドすべき距離を計算する
ここにInterpolator補間器があり、Interpolatorの役割は時間が経つにつれてスライドの距離を計算することです.
1.Viewの描画windowからViewを追加すると、View RootImplから描画が開始されます.2.Viewの描画は、measure、layout、drawのプロセスをほぼ経験します.3.requestLayout()は、Viewツリー全体を再描画(測定、レイアウト、描画)します.invalidate()を呼び出すと、Viewのサイズと位置が変更されない場合は再描画されます.postInvalidate()の役割はinvalidate()と一致しますが、サブスレッドで呼び出すことができます.4.Srcollerがスムーズな移動を実現する原理はInterpolator補間器を使用することである
本文はViewの描画の流れをめぐって分析を行って、本文を見終わってあなたは以下のいくつかの問題を理解するかもしれません:1.setMeasuredDimension()はViewのサイズを決定します.2.requestLayout()、invalidate()およびpostInvalidate()リフレッシュコントロールの違い.3.scrollerの使用と原理.
一、描画プロセス
1. ViewRootImpl.setView()
1.1.WindowManagerImpl.addView()
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
1.2.WindowManagerGlobal.addView()
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// ...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
...
}
}
}
1.3.ViewRootImpl.setView()-->requestLayout()
1.4.ViewRootImpl.performTraversals()
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
// ...
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
//
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
// WindowManager.LayoutParams Weight( ) ,
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
} else {
maybeHandleWindowMove(frame);
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
//
performLayout(lp, mWidth, mHeight);
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
// start out transparent
// TODO: AVOID THAT CALL BY CACHING THE RESULT?
host.getLocationInWindow(mTmpLocation);
mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + host.mRight - host.mLeft,
mTmpLocation[1] + host.mBottom - host.mTop);
host.gatherTransparentRegion(mTransparentRegion);
if (mTranslator != null) {
mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
}
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
mFullRedrawNeeded = true;
// reconfigure window manager
try {
mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
} catch (RemoteException e) {
}
}
}
}
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
//
performDraw();
} else {
if (isViewVisible) {
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
}
2.performMeasure()
2.1.mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
// mPrivateFlags PFLAG_FORCE_LAYOUT
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
//
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
// match_parent
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);
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// flag not set, setMeasuredDimension() was not invoked, we raise
// an exception to warn the developer
if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
throw new IllegalStateException("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
// mPrivateFlags PFLAG_LAYOUT_REQUIRED
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
ここでは、mPrivateFlagsがPFLAG_でない場合、onMeasure()測定コントロールが必要かどうかを判断します.FORCE_LAYOUTでコントロールのサイズが変更されていない場合は、コントロールを再測定する必要はありません.DecorViewはFrameLayoutを継承し、FrameLayoutのonMeasureメソッドを見ているからです.
2.2.FrameLayout.onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
final boolean measureMatchParentChildren =
MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
mMatchParentChildren.clear();
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
// View
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
//
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
if (measureMatchParentChildren) {
if (lp.width == LayoutParams.MATCH_PARENT ||
lp.height == LayoutParams.MATCH_PARENT) {
mMatchParentChildren.add(child);
}
}
}
}
// ...
// maxHeight maxWidth
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
// ...
}
}
サブコントロールを巡回することによって、サブコントロールの最も広い幅と最も高い高さを取得し、setMeasuredDimensionメソッドによってFrameLayoutのmMeasuredWidthとmMeasuredHeightを設定します.つまり、コントロールの幅は最終的にsetMeasuredDimension()メソッドで設定されます.
3.performLayout()
3.1.ViewRootImpl.performLayout()
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
//
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
// layout , requestLayout() ,
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
ArrayList validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
mHandlingLayoutInLayoutRequest = true;
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
//
if (validLayoutRequesters != null) {
final ArrayList finalRequesters = validLayoutRequesters;
// Post second-pass requests to the next frame
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
ここではコントロールを複数回layout操作することがわかりますが、同じようにViewのlayoutメソッドを見てみましょう.
3.2.View.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);
// mPrivateFlags layout layout
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 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 &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
notifyEnterOrExitForAutoFillIfNeeded(true);
}
}
ここでは、コントロールの位置が変更されたかどうかを判断し、どちらか一方を満たしてonLayout操作を行う.
3.3.FrameLayout.onLayout()
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
//
// View View View
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
//
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
巡回サブビューは、サブビューに対応する座標位置をサブビューに伝達する.ViewGroupをカスタマイズするときに再onLayout()メソッドは、義理の子Viewの位置を決めることができます.
3.perforDraw()
最終的にはdrawSoftware()メソッドが実行されます
3.1.ViewRootImpl.drawSoftware()
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
boolean scalingRequired, Rect dirty) {
// Draw with software renderer.
final Canvas canvas;
try {
final int left = dirty.left;
final int top = dirty.top;
final int right = dirty.right;
final int bottom = dirty.bottom;
//
canvas = mSurface.lockCanvas(dirty);
if (left != dirty.left || top != dirty.top || right != dirty.right
|| bottom != dirty.bottom) {
attachInfo.mIgnoreDirtyState = true;
}
canvas.setDensity(mDensity);
} catch (Surface.OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not lock surface", e);
mLayoutRequested = true; // ask wm for a new surface next time.
return false;
}
try {
if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
dirty.setEmpty();
mIsAnimating = false;
mView.mPrivateFlags |= View.PFLAG_DRAWN;
try {
canvas.translate(-xoff, -yoff);
if (mTranslator != null) {
mTranslator.translateCanvas(canvas);
}
canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
attachInfo.mSetIgnoreDirtyState = false;
//
mView.draw(canvas);
drawAccessibilityFocusedDrawableIfNeeded(canvas);
} finally {
if (!attachInfo.mSetIgnoreDirtyState) {
// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false;
}
}
} finally {
try {
//
surface.unlockCanvasAndPost(canvas);
} catch (IllegalArgumentException e) {
Log.e(mTag, "Could not unlock surface", e);
mLayoutRequested = true; // ask wm for a new surface next time.
//noinspection ReturnInsideFinallyBlock
return false;
}
}
return true;
}
3.2.View.draw()
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
// Step 1, draw the background, if needed
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// 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);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
// ...( )
}
描画には、背景の描画、それ自体の描画、サブViewの描画、スクロールバーの描画の順にいくつかのステップがあります.
二、requestLayout()、invalidate()、postInvalidate()の違い
1.requestLayout()
1.1.view.requestLayout()
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
ViewRootImpl viewRoot = getViewRootImpl();
//
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
// mPrivateFlags
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
このことから、requestLayout()メソッドを呼び出すと、mViewにあるmPrivateFlags識別子が再設定されることがわかる.measure()の場合は再測定されます(2.1 mView.measureが見られます)
1.2.viewRootImpl.requestLayout()
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
最終的には、描画プロセスの1.4が実行する.ViewRootImpl.performTraversals().
2.invalidate()
view.invalidate()-->invalidateInternal()-->viewRootImpl. invalidateRectOnScreen()
private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}
// View
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
final float appScale = mAttachInfo.mApplicationScale;
final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
scheduleTraversals();
}
}
ビューのサイズや位置が変更されていない場合は、測定やレイアウトを変更せずにビューを再描画します.
3.postInvalidate()
view.postInvalidate()-->postInvalidateDelayed()-->ViewRootImpl. dispatchInvalidateDelayed()
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
final class ViewRootHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
}
}
}
PostInvalidate()handlerによるMSG_の送信INVALidateメッセージは、最終的に呼び出されるのはinvalidate()メソッドです.
したがって、requestLayout()メソッドはViewツリーの再描画に適しており、invalidate()はViewのサイズと位置が変更されていない場合に描画に適していることをまとめることができます.
三、Scrollerの応用
scrollerは主にコントロールの滑らかなスライドに用いられ、サブViewは描画時にcomputeScroll()を呼び出し、この方法を書き換えてViewを呼び出す.scrollTo(x,y).これにより、コントロールのスライドが実現されます.では、スムーズなスライドを実現するにはどうすればいいのでしょうか.
1.Scrollerの使用
ビューで再computeScroll()を実行し、scrollerを使用してスムーズな移動距離を計算します.
@Override
public void computeScroll() {
// computeScroll() ,
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
2.Scrollerの原理
2.1スライドの方法
一定の時間内に一定の距離を移動する
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}
2.2移動距離の算出方法
時間が経つにつれて現在スライドすべき距離を計算する
public boolean computeScrollOffset() {
if (mFinished) {
return false;
}
//
int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
if (timePassed < mDuration) {
switch (mMode) {
case SCROLL_MODE:
//
final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
case FLING_MODE:
//
final float t = (float) timePassed / mDuration;
final int index = (int) (NB_SAMPLES * t);
float distanceCoef = 1.f;
float velocityCoef = 0.f;
if (index < NB_SAMPLES) {
final float t_inf = (float) index / NB_SAMPLES;
final float t_sup = (float) (index + 1) / NB_SAMPLES;
final float d_inf = SPLINE_POSITION[index];
final float d_sup = SPLINE_POSITION[index + 1];
velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
distanceCoef = d_inf + (t - t_inf) * velocityCoef;
}
mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
// Pin to mMinX <= mCurrX <= mMaxX
mCurrX = Math.min(mCurrX, mMaxX);
mCurrX = Math.max(mCurrX, mMinX);
mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
// Pin to mMinY <= mCurrY <= mMaxY
mCurrY = Math.min(mCurrY, mMaxY);
mCurrY = Math.max(mCurrY, mMinY);
if (mCurrX == mFinalX && mCurrY == mFinalY) {
mFinished = true;
}
break;
}
}
else {
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
}
ここにInterpolator補間器があり、Interpolatorの役割は時間が経つにつれてスライドの距離を計算することです.
public interface TimeInterpolator {
float getInterpolation(float input);
}
まとめ
1.Viewの描画windowからViewを追加すると、View RootImplから描画が開始されます.2.Viewの描画は、measure、layout、drawのプロセスをほぼ経験します.3.requestLayout()は、Viewツリー全体を再描画(測定、レイアウト、描画)します.invalidate()を呼び出すと、Viewのサイズと位置が変更されない場合は再描画されます.postInvalidate()の役割はinvalidate()と一致しますが、サブスレッドで呼び出すことができます.4.Srcollerがスムーズな移動を実現する原理はInterpolator補間器を使用することである