Android自定义一个view ViewRootImpl绘制流程示例
Android如何自定义一个view ViewRootImpl绘制流程
1、自定义属性
在res/values/attrs.xml文件里定义一个declare-styleable name:自定义view的名字
<resources> <declare-styleable name="CustomView"> <attr name="background" format="color"/> //定义属性:名称 + 格式类型---颜色、尺寸、整形、字符串类型 <attr name="size" format="dimension"/> <attr name="AttrFirst" format="integer"/> <attr name="AttrSecond" format="string"/> </declare-styleable> </resources>
2、在res/layout/activity_main.xml文件里使用自定义view
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:custom="http://schemas.android.com/apk/res-auto" // 自定义的命名空间 android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.example.custom.MainActivity"> <com.example.custom.CustomView android:id="@+id/custom_view" android:layout_width="100dp" android:layout_height="100dp" custom:background="@color/colorPrimary" custom:size="20dp" custom:AttrFirst="1" custom:AttrSecond="CustomView"/> </LinearLayout>
3、构造CustomView名称的view,读取自定义属性
//通过AttributeSet直接获取
public CustomView(Context context, AttributeSet attrs) { super(context, attrs); int n = attrs.getAttributeCount(); //获得自定义view的属性数量 for (int i =0; i < n; i++) { Log.d("CustomView", "attrs No " + i + ", name: " + attrs.getAttributeName(i) + ", value: " + attrs.getAttributeValue(i)); } int custon_background = attrs.getAttributeIntValue("http://schemas.android.com/apk/res-auto", "background", Color.BLUE); int custom_size = attrs.getAttributeIntValue("http://schemas.android.com/apk/res-auto", "size", 10); int AttrFirst = attrs.getAttributeIntValue("http://schemas.android.com/apk/res-auto", "AttrFirst", 100)); String AttrSecond = attrs.getAttributeValue("http://schemas.android.com/apk/res-auto", "AttrSecond")); }
//getAttributeName来获取对应index处的属性名称,以sting返回。
//getAttributeValue来获取相应index处属性的值,以string返回。
//getAttributeIntValue来获取相应attribute属性的值
public String getAttributeValue(String namespace, String attribute);
public int getAttributeIntValue(String namespace, String attribute, int defaultValue);
namespace代表自定义属性的命名空间(与xml中的使用方法相同)
public class CustomView extends View { private Paint mPaint; private int mHeight; private int mWidth; private int custom_size; private float scale = 1f; private final int SIZE = 30; public CustomView(Context context) { this(context, null); } public CustomView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomView, defStyleAttr, R.style.AppTheme); //通过obtainStyledAttributes()方法获取属性值 int custon_background = a.getColor(R.styleable.CustomView_background, Color.BLUE); custom_size = a.getDimensionPixelSize(R.styleable.CustomView_size, 10); int AttrFirst = arr.getInt(R.styleable.CustomView_AttrFirst, 100); String AttrSecond = arr.getString(R.styleable.CustomView_AttrSecond); a.recycle(); mPaint = new Paint(); mPaint.setColor(custom_background); mPaint.setAntiAlias(true); mPaint.setStyle(Paint.Style.FILL); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //计算出实际的高(mMeasuredHeight)和宽(mMeasureWidth)传入setMeasuredDimension()方法完成单个View的测量 int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int measuredHeight, measuredWidth; if (widthMode == MeasureSpec.EXACTLY) { measuredWidth = widthSize; } else { measuredWidth = SIZE; } if (heightMode == MeasureSpec.EXACTLY) { measuredHeight = heightSize; } else { measuredHeight = SIZE; } setMeasuredDimension(measuredWidth, measuredHeight); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { //onLayout决定具体View的大小和位置, 对当前视图和其所有子View设置它们在父视图中具体位置 super.onLayout(changed, left, top, right, bottom); mHeight = getHeight(); //getWidth,和getLeft等这些函数都是View相对于其父View的位置。而getMeasuredWidth,getMeasuredHeight是测量后该View的实际值 mWidth = getWidth(); } @Override protected void onDraw(Canvas canvas) { canvas.drawCircle(mWidth/2, mHeight/2, custom_size * scale, mPaint); } private ValueAnimator mAnimator; public void startAnimation() { mAnimator = ValueAnimator.ofFloat(1, 2); mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { scale = (float) animation.getAnimatedValue(); postInvalidate(); } }); mAnimator.setRepeatCount(-1); //重复次数,-1表示无限循环 mAnimator.setRepeatMode(ValueAnimator.REVERSE); //重复模式:RESTART---重新开始,REVERSE---恢复初始状态再开始 mAnimator.start(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); mAnimator.end(); //关闭动画 } @Override protected Parcelable onSaveInstanceState() { return super.onSaveInstanceState(); } @Override protected void onRestoreInstanceState(Parcelable state) { super.onRestoreInstanceState(state); } }
4、自定义view应用属性、刷新显示
Android里,一个view的绘制流程包括: Measure、Layout、Draw 测量——onMeasure():决定View的大小,确定view及它所有子节点需要的尺寸 布局——onLayout():决定View在ViewGroup中的位置, 当view需要为它的所有子节点指定大小和布局时,调用此方法 绘制——onDraw():绘制View。 自定义View中,onLayout配合onMeasure方法一起使用,可以实现自定义View的复杂布局。
自定义view,刷新view的方法: requestLayout()、invalidate()、postInvalidate(), 其实invalidate和postInvalidate这两个方法作用是一样的,唯一不同: invalidate用在主线程 postInvalidate用在异步线程,它最后会通过handler调用invalidate实现
requestLayout和invalidate的内部实现: requestLayout 会调用measure和layout 等一系列操作,然后根据布局是否发生改变,surface是否被销毁,来决定是否调用draw,也就是说requestlayout肯定会调用measure和layout, 但不一定调用draw,读者可以试着改下我上面写的那个小程序,将postInvalidate改成requestlayout,动画效果就消失了,因为布局没有发生改变。 invalidate 只会调用draw,而且肯定会调,即使什么都没有发生改变,它也会重新绘制。 所以如果有布局需要发生改变,需要调用requestlayout方法,如果只是刷新动画,则只需要调用invalidate方法
postInvalidate()方法程序调用流程,从View.java到ViewRootImpl.java,最终由performTraversals()实现 postInvalidate => postInvalidateDelayed => dispatchInvalidateDelayed => invalidate => scheduleTraversals() => doTraversal => performTraversals
requestLayout()方法程序调用流程 requestLayout() => scheduleTraversals() => doTraversal => performTraversals
performTraversals()方法调用:(真正实现view的measure、layout、draw) performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); performMeasure() => measure() => onMeasure() 调用该方法来确定view及它所有子节点需要的尺寸 performLayout(lp, desiredWindowWidth, desiredWindowHeight); performLayout() => layout() => onLayout() 当view需要为它的所有子节点指定大小和布局时,调用此方法 performDraw(); performDraw() => draw() => onDraw() => dispatchDraw() (drawSoftware())
onMeasure(int widthMeasureSpec, int heightMeasureSpec) 功能:通过测量知道一个view要占的大小, 参数:宽高测量规格,int型的值(java中int型由4个字节(32bit)组成),在MeasureSpce中,其前两位表示mode,后30位表示size MeasureSpce的mode有三种:EXACTLY、AT_MOST、UNSPECIFIED
1、当父布局是EXACTLY时, 子控件确定大小或者match_parent,mode都是EXACTLY, 子控件是wrap_content时,mode为AT_MOST;
2、当父布局是AT_MOST时, 子控件确定大小,mode为EXACTLY, 子控件wrap_content或者match_parent时,mode为AT_MOST。 所以在确定控件大小时,需要判断MeasureSpec的mode,不能直接用MeasureSpec的size
View.java:
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { ... public void requestLayout() { if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { // Only trigger request-during-layout logic if this is the view requesting it, // not the views in its parent hierarchy ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot != null && viewRoot.isInLayout()) { if (!viewRoot.requestLayoutDuringLayout(this)) { return; } } mAttachInfo.mViewRequestingLayout = this; } mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); //ViewParent接口的requestLayout()方法 } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; } } public final void measure(int widthMeasureSpec, int heightMeasureSpec) { //为整个View树计算实际的大小, 需要递归的去计算每一个子视图的大小 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); final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY; final boolean matchingSize = isExactly && getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec); if (forceLayout || !matchingSize && (widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec)) { // 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("onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension } //对于每个View的实际宽高都是由父视图和本身视图决定的 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { //计算出实际的高(mMeasuredHeight)和宽(mMeasureWidth)传入setMeasuredDimension()方法完成单个View的测量 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } setMeasuredDimensionRaw(measuredWidth, measuredHeight); } private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; } public void layout(int l, int t, int r, int b) { //为整个View树计算实际的位置, 需要递归的去计算每一个子视图的位置 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回调方法,具体实现由重写了onLayout方法的ViewGroup的子类去实现 onLayout(changed, l, t, r, b); //l,t,r,b四个值是子View相对于父View的值 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; } //onLayout被定义为抽象方法,继承ViewGroup时必须要重写该方法 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { } 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; /* * 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) { 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); // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // we're done... return; } ... // 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); p.setShader(fade); canvas.drawRect(left, top, right, top + length, p); } if (drawBottom) { ... } if (drawLeft) { ... } if (drawRight) { ... } canvas.restoreToCount(saveCount); // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); // 核心 } } protected void dispatchDraw(Canvas canvas) { } public void postInvalidate() { postInvalidateDelayed(0); } public void postInvalidateDelayed(long delayMilliseconds) { // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); } } ... }
ViewRootImpl.java
public final class ViewRootImpl implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { ... final class ViewRootHandler extends Handler { ... @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break; case MSG_INVALIDATE_RECT: final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj; info.target.invalidate(info.left, info.top, info.right, info.bottom); info.recycle(); break; case MSG_PROCESS_INPUT_EVENTS: mProcessInputEventsScheduled = false; doProcessInputEvents(); break; case MSG_DISPATCH_APP_VISIBILITY: handleAppVisibility(msg.arg1 != 0); break; case MSG_DISPATCH_GET_NEW_SURFACE: handleGetNewSurface(); break; case MSG_RESIZED: { // Recycled in the fall through... SomeArgs args = (SomeArgs) msg.obj; if (mWinFrame.equals(args.arg1) && mPendingOverscanInsets.equals(args.arg5) && mPendingContentInsets.equals(args.arg2) && mPendingStableInsets.equals(args.arg6) && mPendingVisibleInsets.equals(args.arg3) && args.arg4 == null) { break; } } // fall through... case MSG_RESIZED_REPORT: if (mAdded) { ... requestLayout(); } break; case MSG_WINDOW_MOVED: if (mAdded) { ... requestLayout(); } break; case MSG_WINDOW_FOCUS_CHANGED: { if (mAdded) { boolean hasWindowFocus = msg.arg1 != 0; mAttachInfo.mHasWindowFocus = hasWindowFocus; profileRendering(hasWindowFocus); if (hasWindowFocus) { boolean inTouchMode = msg.arg2 != 0; ensureTouchModeLocally(inTouchMode); if (mAttachInfo.mHardwareRenderer != null && mSurface.isValid()){ mFullRedrawNeeded = true; try { final WindowManager.LayoutParams lp = mWindowAttributes; final Rect surfaceInsets = lp != null ? lp.surfaceInsets : null; mAttachInfo.mHardwareRenderer.initializeIfNeeded( mWidth, mHeight, mSurface, surfaceInsets); } catch (OutOfResourcesException e) { Log.e(TAG, "OutOfResourcesException locking surface", e); try { if (!mWindowSession.outOfMemory(mWindow)) { Slog.w(TAG, "No processes killed for memory; killing self"); Process.killProcess(Process.myPid()); } } catch (RemoteException ex) { } // Retry in a bit. sendMessageDelayed(obtainMessage(msg.what, msg.arg1, msg.arg2), 500); return; } } } mLastWasImTarget = WindowManager.LayoutParams .mayUseInputMethod(mWindowAttributes.flags); InputMethodManager imm = InputMethodManager.peekInstance(); if (mView != null) { if (hasWindowFocus && imm != null && mLastWasImTarget && !isInLocalFocusMode()) { imm.startGettingWindowFocus(mView); } mAttachInfo.mKeyDispatchState.reset(); mView.dispatchWindowFocusChanged(hasWindowFocus); mAttachInfo.mTreeObserver.dispatchOnWindowFocusChange(hasWindowFocus); } // Note: must be done after the focus change callbacks, // so all of the view state is set up correctly. if (hasWindowFocus) { if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) { imm.onWindowFocus(mView, mView.findFocus(), mWindowAttributes.softInputMode, !mHasHadWindowFocus, mWindowAttributes.flags); } // Clear the forward bit. We can just do this directly, since // the window manager doesn't care about it. mWindowAttributes.softInputMode &= ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; ((WindowManager.LayoutParams)mView.getLayoutParams()) .softInputMode &= ~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; mHasHadWindowFocus = true; } if (mView != null && mAccessibilityManager.isEnabled()) { if (hasWindowFocus) { mView.sendAccessibilityEvent( AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); } } } } break; ... } } } final ViewRootHandler mHandler = new ViewRootHandler(); public void dispatchInvalidateDelayed(View view, long delayMilliseconds) { Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view); mHandler.sendMessageDelayed(msg, delayMilliseconds); } @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; // scheduleTraversals(); // mTraversalRunnable => doTraversal => performTraversals } } @Override public boolean isLayoutRequested() { return mLayoutRequested; } void invalidate() { mDirty.set(0, 0, mWidth, mHeight); if (!mWillDrawSoon) { scheduleTraversals(); // mTraversalRunnable => doTraversal => performTraversals } } final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } final TraversalRunnable mTraversalRunnable = new TraversalRunnable(); void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); } } void unscheduleTraversals() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().removeSyncBarrier(mTraversalBarrier); mChoreographer.removeCallbacks( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); } } void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; mHandler.getLooper().removeSyncBarrier(mTraversalBarrier); if (mProfile) { Debug.startMethodTracing("ViewAncestor"); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals"); try { performTraversals(); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } if (mProfile) { Debug.stopMethodTracing(); mProfile = false; } } } private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); // 调用用View.java的measure()方法 } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } } private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested = false; mScrollMayChange = true; mInLayout = true; final View host = mView; if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { Log.v(TAG, "Laying out " + host + " to (" + host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")"); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); try { host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); // 调用用View.java的layout()方法 mInLayout = false; int numViewsRequestingLayout = mLayoutRequesters.size(); if (numViewsRequestingLayout > 0) { // requestLayout() was called during layout. // If no layout-request flags are set on the requesting views, there is no problem. // If some requests are still pending, then we need to clear those flags and do // a full request/measure/layout pass to handle this situation. ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false); if (validLayoutRequesters != null) { // Set this flag to indicate that any further requests are happening during // the second pass, which may result in posting those requests to the next // frame instead mHandlingLayoutInLayoutRequest = true; // Process fresh layout requests, then measure and layout int numValidRequests = validLayoutRequesters.size(); for (int i = 0; i < numValidRequests; ++i) { final View view = validLayoutRequesters.get(i); Log.w("View", "requestLayout() improperly called by " + view + " during layout: running second layout pass"); view.requestLayout(); } measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight); mInLayout = true; host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); mHandlingLayoutInLayoutRequest = false; // Check the valid requests again, this time without checking/clearing the // layout flags, since requests happening during the second pass get noop'd validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true); if (validLayoutRequesters != null) { final ArrayList<View> 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; } //performDraw() => draw() => drawSoftware() private void performDraw() { if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) { return; } final boolean fullRedrawNeeded = mFullRedrawNeeded; mFullRedrawNeeded = false; mIsDrawing = true; Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw"); try { draw(fullRedrawNeeded); // draw()调用 drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty) // private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } // For whatever reason we didn't create a HardwareRenderer, end any // hardware animations that are now dangling if (mAttachInfo.mPendingAnimatingRenderNodes != null) { final int count = mAttachInfo.mPendingAnimatingRenderNodes.size(); for (int i = 0; i < count; i++) { mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators(); } mAttachInfo.mPendingAnimatingRenderNodes.clear(); } if (mReportNextDraw) { mReportNextDraw = false; if (mAttachInfo.mHardwareRenderer != null) { mAttachInfo.mHardwareRenderer.fence(); } if (LOCAL_LOGV) { Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle()); } if (mSurfaceHolder != null && mSurface.isValid()) { mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder); SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { if (c instanceof SurfaceHolder.Callback2) { ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( mSurfaceHolder); } } } } try { mWindowSession.finishDrawing(mWindow); } catch (RemoteException e) { } } } ... }
以上就是Android自定义一个view ViewRootImpl绘制流程示例的详细内容,更多关于Android view ViewRootImpl绘制的资料请关注脚本之家其它相关文章!
相关文章
Android中使用SQLite3 命令行查看内嵌数据库的方法
这篇文章主要介绍了Android中使用SQLite3 命令行查看内嵌数据库的方法的相关资料,需要的朋友可以参考下2015-12-12Android convinientbanner顶部广告轮播控件使用详解
这篇文章主要为大家详细介绍了Android convinientbanner顶部广告轮播控件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下2017-01-01
最新评论