就像上个文章说的,触摸事件的传递机制是从外层到内层的过程。

我们想来看看这个页面里面的层级关系:



以下我们就用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开始的绘制流程,请看下篇。

更多内容:demanmath

公共号:

Android View的绘制机制前世今生---前世的更多相关文章

  1. Android View的绘制机制流程深入详解(四)

    本系列文章主要着重深入介绍Android View的绘制机制及流程,第四篇主要介绍Android自定义View及ViewGroup的实现方法和流程. 主要介绍了自绘控件.自定义组合控件.自定义继承控件 ...

  2. Android View的绘制机制流程深入详解(三)

    本系列文章主要着重深入介绍Android View的绘制机制及流程,第三篇主要介绍并分析视图状态以及重绘流程,首先剖析了 视图的几种状态,然后在深入分析视图的重绘机制流程. 真题园网:http://w ...

  3. Android View的绘制机制流程深入详解(二)

    本系列文章主要着重深入介绍Android View的绘制机制及流程,第二篇主要介绍并分析Android视图的绘制的原理和流程.主要从 onMeasure().onLayout()和onDraw()这三 ...

  4. Android View的绘制机制流程深入详解(一)

    本系列文章主要着重深入介绍Android View的绘制机制及流程,第一篇主要介绍并分析LayoutInflater的原理, 从而理解setContentView的加载原理.对于LayoutInfla ...

  5. Android 中View的绘制机制源代码分析 三

    到眼下为止,measure过程已经解说完了,今天開始我们就来学习layout过程.只是在学习layout过程之前.大家有没有发现我换了编辑器,哈哈.最终下定决心从Html编辑器切换为markdown编 ...

  6. Android 中View的绘制机制源代码分析 一

    尊重原创: http://blog.csdn.net/yuanzeyao/article/details/46765113 差点儿相同半年没有写博客了,一是由于工作比較忙,二是认为没有什么内容值得写, ...

  7. Android 中View的绘制机制源代码分析 二

    尊重原创:http://blog.csdn.net/yuanzeyao/article/details/46842891 本篇文章接着上篇文章的内容来继续讨论View的绘制机制,上篇文章中我们主要解说 ...

  8. Android View 如何绘制

    上文说道了Android如何测量,但是一个漂亮的控件我只知道您长到哪儿,这当然不行.只需要简单重写OnDraw方法,并在Canvas(画布)对象上调用那根五颜六色的画笔就能够画出这控件"性感 ...

  9. Android View 的绘制流程之 Layout 和 Draw 过程详解 (二)

    View 的绘制系列文章: Android View 的绘制流程之 Measure 过程详解 (一) Android View 绘制流程之 DecorView 与 ViewRootImpl 在上一篇  ...

随机推荐

  1. html 小游戏合集(1.0)

    最近做了个小游戏合集,有点沙雕,毕竟是1.0,将就看看. <!DOCTYPE html> <html> <head> <meta charset=" ...

  2. 【记】Linux下安装JDK1.7

    Java官网已经不提供除最新版本以外版本的JDK下载了,下载JDK1.7,密码: rsqg 本地Linux系统为Centos6.9,本身就没安装Java:已安装Java需要先卸载,卸载方法请百度. 1 ...

  3. vue兄弟组件传值——事件总线

    1.创建一个js文件,例如msg.js,放到合适位置,例如components中,或者其他位置也行.然后在兄弟两个组件中分别引入msg.js文件 msg.js: import Vue from 'vu ...

  4. 更好用的 Python 任务自动化工具:nox 官方教程

    英文| nox tutorial 出处| nox 官方文档 译者| 豌豆花下猫@Python猫 Github地址:https://github.com/chinesehuazhou/nox_doc_c ...

  5. Task 线程重用导致等待!

    测试代码: ; i < ; i++) { var d = DateTime.Now; Task.Run(() => { Console.WriteLine($"{Thread.C ...

  6. 【JavaScript 基础知识】一篇关于 JavaScript 一些知识点的总结

    JavaScript 中基础数据类型  数据类型名称  数据类型说明 Undefined 只有一个值,即 undefined ,声明变量的初始值. Null 只有一个值,即 null ,表示空指针,  ...

  7. hadoop各版本hadoop.dll和winutils.exe缺少这两个文件

    1.1 缺少winutils.exeCould not locate executable null \bin\winutils.exe in the hadoop binaries1.2 缺少had ...

  8. 洛谷 P5424 [USACO19OPEN]Snakes

    题目链接 题目描述 传说,数千年前圣帕特里克消灭了哞尔兰所有的蛇.然而,蛇们现在卷土重来了!圣帕特里克节是在每年的3月17日,所以Bessie要用彻底清除哞尔兰所有的蛇来纪念圣帕特里克. Bessie ...

  9. 将jar包安装到本地仓库

    通过cmd切换到apache maven 的bin目录 mvn install:install-file -DgroupId=com.antgroup.zmxy -DartifactId=zmxy-s ...

  10. [bzoj3925] [洛谷P3343] [ZJOI2015] 地震后的幻想乡

    Description 傲娇少女幽香是一个很萌很萌的妹子,而且她非常非常地有爱心,很喜欢为幻想乡的人们做一些自己力所能及的事情来帮助他们. 这不,幻想乡突然发生了地震,所有的道路都崩塌了.现在的首要任 ...