内容简介:自定义View的话,常见过程如下:三个构造方法+三个可以复写的方法。我们先看下这3个方法的顺序:
就像上个文章说的,触摸事件的传递机制是从外层到内层的过程。
我们想来看看这个页面里面的层级关系:
以下我们就用what-how-why三部曲的方式来分析View的绘制过程。
由于篇幅很大,所以分几篇来解析这个过程。
这篇主要是自定义view/viewgroup,以及从Activity到DecorView的加载过程。
1.what:怎么自定义一个View
1.1自定义View
自定义View的话,常见过程如下:
/** * @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个方法的顺序:
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)]
1.2自定义ViewGroup
上代码
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) } }
效果如下:
这里override了layout方法。可见View的绘制跟他的父View只有一个关系,ViewGroup指定了子View的位置。
关于View/ViewGroup绘制的机制,在下一节讨论。
2.How:View的绘制机制是什么
从上一节看出:整个绘制流程三个过程,measure,layout,draw这三个过程。
下面我们从源码的角度来分析下是不是这个过程。
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(); } } }
2.1 关键是页面绘制流程
整个的过程就是一开始讲的层级关系。
第一点:performResumeActivity 比wm.addView(decor, l)先执行。所以Activity是先获取焦点,才绘制view。
performResumeActivity->r.activity.performResume()->mInstrumentation.callActivityOnResume(this)->activity.onResume()
在performResume最后可以看到onPostResume
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的过程。
//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的实现就是WindowManagerImpl
@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开始的绘制流程,请看下篇。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 基于源码分析 Android View 绘制机制
- RecyclerView 源码深入解析——绘制流程、缓存机制、动画等
- ViewGroup 默认顺序绘制子 View,如何修改?什么场景需要修改绘制顺序?
- Shader 绘制基础图形
- css绘制特殊图形
- View 绘制流程分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。