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

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



以下我们就用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. Markdown 复杂公式&常用符号

    公式格式 行内公式 行内公式(不会换行)使用 $ 作为起止符,例如:$a + b = c$, 效果为:\(a + b = c\) 块级公式 块级公式(单独一行)使用 $$ 作为起止符,例如:$$a + ...

  2. Linux磁盘管理之LVM逻辑卷快照

    一.快照的工作原理 所谓快照就是将当时的系统数据记录下来,在未来若有数据变动,则会将变更前的数据放入快照区进行保存.我们可理解为快照就是给系统拍了一张照片,记录当时系统在拍快照的状态.只不过现实生活中 ...

  3. 探究Dubbo的拓展机制: 下

    承接上篇, 本篇博文的主题就是认认真真捋一捋, 看一下 Dubbo是如何实现他的IOC / AOP / 以及Dubbo SPI这个拓展点的 总览: 本篇的话总体上分成两部分进行展开 第一点就是 Dub ...

  4. Java之IO流用法总结

    Java的IO流概述:1.I/O是Input/Output的缩写,I/O技术是非常实用的技术,用于处理设备之间的数据传输.如读/写文件,网络通讯等.2.Java程序中,对于数据的输入/输出操作以“流( ...

  5. 【一起学源码-微服务】Hystrix 源码一:Hystrix基础原理与Demo搭建

    说明 原创不易,如若转载 请标明来源! 欢迎关注本人微信公众号:壹枝花算不算浪漫 更多内容也可查看本人博客:一枝花算不算浪漫 前言 前情回顾 上一个系列文章讲解了Feign的源码,主要是Feign动态 ...

  6. 如何用visual studio code更好的编写python

    目录 1.先决条件 2.Visual Studio Code扩展安装Python 3.Visual Studio Code扩展安装Python for VSCode 4.Visual Studio C ...

  7. Spring Boot2 系列教程 (十四) | 统一异常处理

    如题,今天介绍 SpringBoot 是如何统一处理全局异常的.SpringBoot 中的全局异常处理主要起作用的两个注解是 @ControllerAdvice 和 @ExceptionHandler ...

  8. 如何选择kmeans中的k值——肘部法则–Elbow Method和轮廓系数–Silhouette Coefficient

    肘部法则–Elbow Method 我们知道k-means是以最小化样本与质点平方误差作为目标函数,将每个簇的质点与簇内样本点的平方距离误差和称为畸变程度(distortions),那么,对于一个簇, ...

  9. webpack进阶用法你都get到了么?

    如何消除无用代码:打包自己的私有js库:实现代码分割和动态import提升初次加载速度:配置eslint规范团队代码规范:打包异常抓捕你都get到了么? 摇树优化:Tree Shaking webpa ...

  10. Kdenlive-简单的操作

    版权声明:原创文章,未经博主允许不得转载 前章:https://www.cnblogs.com/weilinfox/p/12246123.html 尽管是简单操作,但内容比较多.可以一边自己尝试编辑一 ...