• 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,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
    公共号:

  • 相关阅读:
    LeetCode 100. 相同的树(Same Tree) 2
    LeetCode 680. 验证回文字符串 Ⅱ(Valid Palindrome II) 1
    MySQL索引操作
    MySQL数据库的一些方法使用
    Anaconda安装新模块
    源码下载
    mongodb内建角色
    windows server 2008开启共享文件设置
    MySQL配置说明
    MySQL的连接数
  • 原文地址:https://www.cnblogs.com/deman/p/12331021.html
Copyright © 2020-2023  润新知