就像上個文章說的,觸摸事件的傳遞機制是從外層到內層的過程。
咱們想來看看這個頁面裏面的層級關係:
如下咱們就用what-how-why三部曲的方式來分析View的繪製過程。
因爲篇幅很大,因此分幾篇來解析這個過程。
這篇主要是自定義view/viewgroup,以及從Activity到DecorView的加載過程。java
自定義View的話,常見過程以下:android
/** * @author DemanMath * @date 2020-02-16 * */ class CustomView : View { constructor(context: Context):super(context) constructor(context: Context,attributeSet: AttributeSet):super(context,attributeSet) constructor(context: Context,attributeSet: AttributeSet,def:Int):super(context,attributeSet,def) override fun onDraw(canvas: Canvas?) { super.onDraw(canvas) AppLog.i() } override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) AppLog.i() } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) AppLog.i() } }
三個構造方法+三個能夠複寫的方法。
咱們先看下這3個方法的順序:canvas
2020-02-16 13:50:28.212 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onMeasure: [at (CustomView.kt:32)] 2020-02-16 13:50:28.222 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onMeasure: [at (CustomView.kt:32)] 2020-02-16 13:50:28.253 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onMeasure: [at (CustomView.kt:32)] 2020-02-16 13:50:28.255 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onMeasure: [at (CustomView.kt:32)] 2020-02-16 13:50:28.259 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onLayout: [at (CustomView.kt:27)] 2020-02-16 13:50:28.403 23141-23141/com.joyfulmath.androidarchitecture I/Arch_App.CustomView: onDraw: [at (CustomView.kt:22)]
上代碼app
package com.joyfulmath.androidarchitecture.view import android.content.Context import android.util.AttributeSet import android.view.ViewGroup import com.joyfulmath.androidarchitecture.base.AppLog import kotlin.math.PI import kotlin.math.cos import kotlin.math.min import kotlin.math.sin /** * @author DemanMath * @date 2020-02-16 * */ class FerrisWheel:ViewGroup { var count = 12 var a = 2*PI/count var startA = PI/2 constructor(context: Context):super(context){ initViews() } constructor(context: Context,attributeSet: AttributeSet):super(context,attributeSet){ initViews() } constructor(context: Context,attributeSet: AttributeSet,def:Int):super(context,attributeSet,def){ initViews() } private fun initViews() { for(i in 0 until count){ this.addView(CustomView(context)) } } override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { var mViewWidth = measuredWidth var mViewHeight = measuredHeight AppLog.i("$mViewWidth,$mViewHeight") var cx = mViewWidth/2 var cy = mViewHeight/2 var r = min(measuredWidth,measuredHeight)*0.5f -20 AppLog.i("r:$r,cx:$cx") for(i in 0 until count){ var view = getChildAt(i) var width = view.measuredWidth var height = view.measuredHeight var cx1 = r* sin(startA+a*i) var cy1 = -r* cos(startA+a*i) AppLog.i("width:$width,height:$height") AppLog.i("cx1:$cx1,cy1:$cy1") view.layout(cx+(cx1-width/2).toInt(), cy+(cy1-height/2).toInt(), cx+(cx1+width/2).toInt(), cy+(cy1+height/2).toInt()) } } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { measureChildren(widthMeasureSpec,heightMeasureSpec) super.onMeasure(widthMeasureSpec, heightMeasureSpec) } }
效果以下:
ide
這裏override了layout方法。可見View的繪製跟他的父View只有一個關係,ViewGroup指定了子View的位置。
關於View/ViewGroup繪製的機制,在下一節討論。oop
從上一節看出:整個繪製流程三個過程,measure,layout,draw這三個過程。
下面咱們從源碼的角度來分析下是否是這個過程。gradle
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ....... // TODO Push resumeArgs into the activity for consideration r = performResumeActivity(token, clearHide, reason); if (r != null) { final Activity a = r.activity; if (localLOGV) Slog.v( TAG, "Resume " + r + " started activity: " + a.mStartedActivity + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished); final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window. boolean willBeVisible = !a.mStartedActivity; if (!willBeVisible) { try { willBeVisible = ActivityManager.getService().willActivityBeVisible( a.getActivityToken()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (r.mPreserveWindow) { a.mWindowAdded = true; r.mPreserveWindow = false; // Normally the ViewRoot sets up callbacks with the Activity // in addView->ViewRootImpl#setView. If we are instead reusing // the decor view we have to notify the view root that the // callbacks may have changed. ViewRootImpl impl = decor.getViewRootImpl(); if (impl != null) { impl.notifyChildRebuilt(); } } if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); } else { // The activity will get a callback for this {@link LayoutParams} change // earlier. However, at that time the decor will not be set (this is set // in this method), so no action will be taken. This call ensures the // callback occurs with the decor set. a.onWindowAttributesChanged(l); } } // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible. } else if (!willBeVisible) { if (localLOGV) Slog.v( TAG, "Launch " + r + " mStartedActivity set"); r.hideForNow = true; } // Get rid of anything left hanging around. cleanUpPendingRemoveWindows(r, false /* force */); // The window is now visible if it has been added, we are not // simply finishing, and we are not starting another activity. if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) { if (r.newConfig != null) { performConfigurationChangedForActivity(r, r.newConfig); if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig); r.newConfig = null; } if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward); WindowManager.LayoutParams l = r.window.getAttributes(); if ((l.softInputMode & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != forwardBit) { l.softInputMode = (l.softInputMode & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) | forwardBit; if (r.activity.mVisibleFromClient) { ViewManager wm = a.getWindowManager(); View decor = r.window.getDecorView(); wm.updateViewLayout(decor, l); } } r.activity.mVisibleFromServer = true; mNumVisibleActivities++; if (r.activity.mVisibleFromClient) { r.activity.makeVisible(); } } if (!r.onlyLocalRequest) { r.nextIdle = mNewActivities; mNewActivities = r; if (localLOGV) Slog.v( TAG, "Scheduling idle handler for " + r); Looper.myQueue().addIdleHandler(new Idler()); } r.onlyLocalRequest = false; // Tell the activity manager we have resumed. if (reallyResume) { try { ActivityManager.getService().activityResumed(token); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } } else { // If an exception was thrown when trying to resume, then // just end this activity. try { ActivityManager.getService() .finishActivity(token, Activity.RESULT_CANCELED, null, Activity.DONT_FINISH_TASK_WITH_ACTIVITY); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } } }
整個的過程就是一開始講的層級關係。
第一點:performResumeActivity 比wm.addView(decor, l)先執行。因此Activity是先獲取焦點,才繪製view。
performResumeActivity->r.activity.performResume()->mInstrumentation.callActivityOnResume(this)->activity.onResume()
在performResume最後能夠看到onPostResumeui
final void performResume() { performRestart(); ... // mResumed is set by the instrumentation mInstrumentation.callActivityOnResume(this); ... onPostResume(); ... } protected void onPostResume() { final Window win = getWindow(); if (win != null) win.makeActive(); if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true); mCalled = true; }
window出現了,這個就是phonewindow。
下面咱們去看docorview的過程。this
//2020.02.18 phonewindow在這裏獲取 if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); //2020.02.18 docorview在這裏獲取 View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; ... if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); } else { // The activity will get a callback for this {@link LayoutParams} change // earlier. However, at that time the decor will not be set (this is set // in this method), so no action will be taken. This call ensures the // callback occurs with the decor set. a.onWindowAttributesChanged(l); } }
咱們來看下wm.addView(decor, l);這個的過程。wm的實現就是WindowManagerImpl3d
@Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); }
mGlobal是WindowManagerGlobal, addview的核心代碼以下
root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); root.setView(view, wparams, panelParentView);
關於從ViewGroup開始的繪製流程,請看下篇。
更多內容:demanmath
公共號: