Android View的绘制机制前世今生---前世

就像上个文章说的,触摸事件的传递机制是从外层到内层的过程。
我们想来看看这个页面里面的层级关系:

以下我们就用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
公共号:

原文地址:https://www.cnblogs.com/deman/p/12331021.html

时间: 2024-10-10 02:06:26

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

从源码角度分析Android View的绘制机制(一)

在Android的学习道路上,每一个人员都免不了去翻阅Android的源码,因为只有从源码的角度分析问题,我们才能真正的玩转Android开发.最近由于工作比较闲,总想着想写点什么东西,正好自己也可以整理一下.考虑到view的显示机制是自定义view的基础,也是面试中经常被问到的问题,所以记录此文,和大家共享,因水平有限,望大家踊跃拍砖,不胜感激. 有过自定义view的同行们都应该知道,view的显示依托于activity的setContentView方法依附到PhoneWindow窗体上的,在

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

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

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

尊重原创: http://blog.csdn.net/yuanzeyao/article/details/46765113 差不多半年没有写博客了,一是因为工作比较忙,二是觉得没有什么内容值得写,三是因为自己越来越懒了吧,不过最近我对Android中View的绘制机制有了一些新的认识,所以想记录下来并分享给大家.在之后的几篇博客中,我会给大家分享如下的内容: 1.View中measure(),layout(),draw()函数执行过程分析,带领大家详细分析View的尺寸测量过程,位置计算,并最终

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

到眼下为止,measure过程已经解说完了,今天開始我们就来学习layout过程.只是在学习layout过程之前.大家有没有发现我换了编辑器,哈哈.最终下定决心从Html编辑器切换为markdown编辑器.这里之所以使用"下定决心"这个词.是由于毕竟Html编辑器使用好几年了.非常多习惯都已经养成了,要改变多年的习惯确实不易.相信这也是还有非常多人坚持使用Html编辑器的原因. 这也反应了一个现象.当人对某一事物非常熟悉时,一旦出现了新的事物想代替老的事物时,人们都有一种抵触的情绪,做

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

到目前为止,measure过程已经讲解完了,今天开始我们就来学习layout过程,不过在学习layout过程之前,大家有没有发现我换了编辑器,哈哈,终于下定决心从Html编辑器切换为markdown编辑器,这里之所以使用"下定决心"这个词,是因为毕竟Html编辑器使用好几年了,很多习惯都已经养成了,要改变多年的习惯确实不易,相信这也是还有很多人坚持使用Html编辑器的原因.这也反应了一个现象,当人对某一事物非常熟悉时,一旦出现了新的事物想取代老的事物时,人们都有一种抵触的情绪,做技术的

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

尊重原创:http://blog.csdn.net/yuanzeyao/article/details/46842891 本篇文章接着上篇文章的内容来继续讨论View的绘制机制,上篇文章中我们主要讲解了View的measure过程,今天我们就来学习ViewGroup的measure过程,由于ViewGroup只是一个抽象类,所以我们需要以一个具体的布局来分析measure过程,正如我上篇文章说的,我打算使用LinearLayout为例讲解measure过程,如果你还没有读过上篇文章,那么建议你先

Android View的绘制流程三部曲 —— Measure

在刚开始学习Java的时候,我看的是Mars老师的视频.Mars老师说过的一句话让我印象很深刻:要有一颗面向对象的心. 如果我们用面向对象的思维方式来思考,就会觉的View的绘制机制是很合理,很科学的.我们要在一张纸上画一幅画,首先要测量一下这幅画有多大吧,然后确定在这张纸的哪个地方画会显得比较美观,最后才是用画笔工具将画绘制在纸上. 在Android中也是一样的.View的绘制流程主要是指measure,layout,draw这三步,即测量,布局,绘制.首先是要测量View的宽高,然后布局确定

Android View 如何绘制

上文说道了Android如何测量,但是一个漂亮的控件我只知道您长到哪儿,这当然不行.只需要简单重写OnDraw方法,并在Canvas(画布)对象上调用那根五颜六色的画笔就能够画出这控件"性感"的外表.那么View又是如何进行绘制了? 要了解View如何绘制,就需要了解canvas(画布)是什么?paint(画笔)能够做什么. Ⅰ.canvas就是表示一块画布,你可以在上面画你所朝思暮想的东西.当我们重写onDraw方法的时候,就能够拿到一个Canvas对象,这个就是你的舞台,画你所思所