但使书种多,会有岁稔时。这篇文章主要讲述Android View的绘制机制前世今生---前世相关的知识,希望能为你提供帮助。
就像上个文章说的,触摸事件的传递机制是从外层到内层的过程。
我们想来看看这个页面里面的层级关系:
文章图片
以下我们就用what-how-why三部曲的方式来分析View的绘制过程。
由于篇幅很大,所以分几篇来解析这个过程。
这篇主要是自定义view/viewgroup,以及从Activity到DecorView的加载过程。
1.what:怎么自定义一个View 1.1自定义View自定义View的话,常见过程如下:
/**
*@authorDemanMath
*@date2020-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.viewimport 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/**
*@authorDemanMath
*@date2020-02-16
*
*/
class FerrisWheel:ViewGroup {var count = 12
var a = 2*PI/count
var startA = PI/2constructor(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);
}
}
【Android View的绘制机制前世今生---前世】我们来看下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开始的绘制流程,请看下篇。
更多内容:demanmath
公共号:
文章图片
推荐阅读
- Android 开发环境搭建与编译
- 归档"xxx"不是来自apple
- 从 MappedFile 的单元测试看 mmap
- This usually happens because your environment has changed since running `npm install`.
- Uni-App - 自定义组件 - 自定义组件创建及使用
- Mybatis中mapper.xml中的模糊查询
- Android开发 碎片Fragment的API全解与标准使用
- AndroidStudio虚拟机 "A system image must be selected to continue"问题解决
- spring boot集成mybatis 自动生成实体类和mapper文件Dao层