主頁 > 移動端開發 > View的繪制流程

View的繪制流程

2021-04-18 10:32:20 移動端開發

文章目錄

  • 前言
  • ViewRootImpl 簡單介紹
  • performMeasure
    • MeasureSpec
    • 測量大小原始碼分析
  • performLayout
  • performDraw
  • setWillNotDraw
  • 多次invalidate會怎么樣
  • 參考

前言

本文基于Android 21原始碼講解
我們的首先簡單了解Activity創建流程

//ActivityThread.java

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
	//創建一個activity物件,內部會呼叫activity的oncreate函式
	Activity a = performLaunchActivity(r, customIntent);
	//后文再繼續分析 
	handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed);
                    
}

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
 

        Activity activity = null;
        
        //Activity的context真正實作類,Activity雖然繼承了Context類但是并沒有實作具體方法而是交給自身的Context mBase;屬性實作
    	Context appContext = createBaseContextForActivity(r, activity);
    	   
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        //創建一個activity物件
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
                

        //呼叫attach構建內部的一個mWindows屬性物件實體  
        activity.attach(appContext, this, getInstrumentation(), r.token,
                r.ident, app, r.intent, r.activityInfo, title, r.parent,
                r.embeddedID, r.lastNonConfigurationInstances, config,
                r.voiceInteractor);
	//呼叫activity的oncreate函式
	 if (r.isPersistable()) {
		mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState); 				
    } else {      
     	mInstrumentation.callActivityOnCreate(activity, r.state);
  	}        

   return activity;
}   

我們繼續看activity,attach函式

//Activity.java
class Activity{
    private Window mWindow;
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, IVoiceInteractor voiceInteractor) {
        //給自己的Context mBase;賦值    
        attachBaseContext(context);
		//我繼續跟如PolicyManager
        mWindow = PolicyManager.makeNewWindow(this);
		
	//WindowManager的賦值其實作類為WindowManagerImpl
		mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
       	
        mWindowManager = mWindow.getWindowManager();
	}
}
//PolicyManager.java

public final class PolicyManager {
    private static final String POLICY_IMPL_CLASS_NAME =
        "com.android.internal.policy.impl.Policy";
    
    private static final IPolicy sPolicy;
    
    static {
			//反射創建com.android.internal.policy.impl.Policy物件
            Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
            sPolicy = (IPolicy)policyClass.newInstance();
        
    }
    
    private PolicyManager() {}
   
    public static Window makeNewWindow(Context context) {
    	//sPolicy實作類為com.android.internal.policy.impl.Policy
        return sPolicy.makeNewWindow(context);
    }
    public static LayoutInflater makeNewLayoutInflater(Context context) {
        return sPolicy.makeNewLayoutInflater(context);
    }
    public static WindowManagerPolicy makeNewWindowManager() {
        return sPolicy.makeNewWindowManager();
    }
    public static FallbackEventHandler makeNewFallbackEventHandler(Context context) {
        return sPolicy.makeNewFallbackEventHandler(context);
    }
}

網上的一些在線原始碼貼出的PolicyManager.java并不正確,具體看下面的google倉庫鏈接PolicyManager.java原始碼鏈接

PolicyManager.java任然是代理類實際實作是com.android.internal.policy.impl.Policy

Policy.java原始碼鏈接

//Policy.java
public class Policy implements IPolicy {
    private static final String TAG = "PhonePolicy";
    private static final String[] preload_classes = {
        "com.android.internal.policy.impl.PhoneLayoutInflater",
        "com.android.internal.policy.impl.PhoneWindow",
        "com.android.internal.policy.impl.PhoneWindow$1",
        "com.android.internal.policy.impl.PhoneWindow$ContextMenuCallback",
        "com.android.internal.policy.impl.PhoneWindow$DecorView",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
        "com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
    };
    static {
        // For performance reasons, preload some policy specific classes when
        // the policy gets loaded.
        for (String s : preload_classes) {
            try {
                Class.forName(s);
            } catch (ClassNotFoundException ex) {
                Log.e(TAG, "Could not preload class for phone policy: " + s);
            }
        }
    }
    public PhoneWindow makeNewWindow(Context context) {
        return new PhoneWindow(context);
    }
    public PhoneLayoutInflater makeNewLayoutInflater(Context context) {
        return new PhoneLayoutInflater(context);
    }
    public PhoneWindowManager makeNewWindowManager() {
        return new PhoneWindowManager();
    }
}

綜上Activity中的mWindow物件實作類為PhoneWindow

我們繼續Activity的onCreate函式

class MainActivity : Activity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    //跟入
    setContentView(R.layout.activity_main)

  }
}

//Activity.java
 public void setContentView(int layoutResID) {
    //getWindow()回傳 Window mWindow;也就是PhoneWindow
    getWindow().setContentView(layoutResID);
    //初始化標題欄相關,這里和本文無關聯
    initWindowDecorActionBar();
 }

//PhoneWindow.java
class PhoneWindow{
	
	//mContentParent是mDecor的子view
	private ViewGroup mContentParent;
	//DecorView物件繼承自FrameLayout,有一個子view,子view為mContentParent
	private DecorView mDecor;

    @Override
    public void setContentView(int layoutResID) {
    	//第一次當然為空   
        if (mContentParent == null) {
        	//很顯然這里必須要給mContentParent賦值
        	//我們進入看看
        	//這里就是給mDecor和mContentParent賦值
            installDecor();
        } 

       //填充布局,并插入mContentParent作為子視圖
       //布局填充器內容比較簡單,讀者可以訪問博主的其他文章
       mLayoutInflater.inflate(layoutResID, mContentParent);
       //回呼監聽器,這里會回呼到activity的onContentChanged函式哦
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
        	//onContentChanged函式回呼時布局一定是被設定的了.
        	//你可以隨意findViewById等操作
            cb.onContentChanged();
        }
    }
    
}

我們具體分析下installDecor函式

//PhoneWindow.java
class PhoneWindow{
	private ViewGroup mContentParent;
	private DecorView mDecor;
	private void installDecor() {
		 //第一次進入時mDecor必然為空
		 if (mDecor == null) {
            mDecor = generateDecor();
          }
				
	}
	private void installDecor() {

        if (mDecor == null) {
        	//就是直接new了一個DecorView回傳
        	//DecorView是FrameLayout,也就是說他是一個布局
            mDecor = generateDecor();  
        }
        
        if (mContentParent == null) {
        	//重點函式,給FrameLayout添加一個子view,
        	//這個子view會通過generateLayout回傳
            mContentParent = generateLayout(mDecor);    
        }
    }
		
	protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

    protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.

        //獲取當前風格
        TypedArray a = getWindowStyle();

      
        //當前是浮窗模式
        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);

        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());

        //根據是否為浮窗模式設定一些屬性
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }
        //根據當前是否需要標題欄設定屬性
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }
        //其他屬性的判斷設定.可參閱其他文章
        if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
            requestFeature(FEATURE_ACTION_BAR_OVERLAY);
        }
        //.....
        //其他屬性的判斷設定.可參閱其他文章略

        
     

        int layoutResource;

        int features = getLocalFeatures();
        //requestFeature會改變getLocalFeatures回傳值哦
        //getLocalFeatures可以到當前設定的一些視窗屬性,如無標題
        //這里通過視窗的一些特點設定
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } 
        //...省略其他else if
        else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            //默認情況下得到的布局
            layoutResource = R.layout.screen_simple;
           
        }

        //填充view
        View in = mLayoutInflater.inflate(layoutResource, null);
        //添加decorview中
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        //mContentRoot就是我們填充的布局
        mContentRoot = (ViewGroup) in;
        //ID_ANDROID_CONTENT為 com.android.internal.R.id.content
        //這里可以得出一個結論不過你設定了何種視窗風格得到布局,一定會有一個子view,id為ID_ANDROID_CONTENT為
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
       

       

        return contentParent;
    }

}

通過上面的原始碼分析我們就可以到一個非常經典的圖:

在這里插入圖片描述
圖片轉載自(實在懶得畫)
Android應用層View繪制流程與原始碼分析

順帶看一眼默認布局

<!--screen_simple.xml-->
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>


上面的內容可以讓我們對Android視圖層次體系有一個具體的概念.
繼續回頭看看handleLaunchActivity函式哦

//ActivityThread.java

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
	//上文已經講解
	Activity a = performLaunchActivity(r, customIntent);
	//繼續深入分析 
	handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed);
                    
}
  final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
       
        //執行activity的onresume函式
        ActivityClientRecord r = performResumeActivity(token, clearHide);

        if (r != null) {
            final Activity a = r.activity;

            if (r.window == null && !a.mFinished && willBeVisible) {
                r.window = r.activity.getWindow();

                View decor = r.window.getDecorView();
                
                decor.setVisibility(View.INVISIBLE);
                //WindowManager 繼承 ViewManager
                //還記得我們怎么得到WindowManager嗎?可以回顧上文
                ViewManager wm = a.getWindowManager();
                
                WindowManager.LayoutParams l = r.window.getAttributes();
                
                a.mDecor = decor;
                
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //WindowManager的實作類是WindowManagerImpl,
                    wm.addView(decor, l);
                }

        } 
    }
//WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();


    @Override
    public void addView(View view, ViewGroup.LayoutParams params) {
    //發現還是一個代理,進入WindowManagerGlobal
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

}
//WindowManagerGlobal.java
public final class WindowManagerGlobal {
	private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
  
       
    //view是decorview哦
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        
        //布局引數
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        //根據一些條件設定布局引數
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            if (context != null
                    && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;

        View panelParentView = null;
        //ViewRootImpl包含繪制繪制的開始
        root = new ViewRootImpl(view.getContext(), display);
        //給decorview設定布局引數
        view.setLayoutParams(wparams);

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
       
        root.setView(view, wparams, panelParentView);
       
    }
}

ViewRootImpl內部包含繪制流程的開始,會在下一個vssync信號到達的時候進行繪制.

不過我們先總結上面原始碼.

綜上所得我們Activity在oncreateonresume回呼的時候根本沒有走繪制流程,所以我們經常遇到獲取高度為0的情況.


class MainActivity : Activity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val view: View = findViewById(R.id.root)
    
    log("onCreate:"+view.width)
  }

  override fun onResume() {
    super.onResume()
    val view:View = findViewById(R.id.root)
    log("onResume:"+view.width)
  }

  
  fun log(msg:String){
    Log.e("MainActivity","${msg}")
  }
}

輸出

onCreate:0
onResume:0

ViewRootImpl 簡單介紹

上文我們最后看到構造了ViewRootImpl然后呼叫setview函式將decorview放入

 //view為decorview
 root = new ViewRootImpl(view.getContext(), display);
 //給decorview設定布局引數
 view.setLayoutParams(wparams);
 root.setView(view, wparams, panelParentView);
//ViewRootImpl.java
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks{
}

可以看到ViewRootImpl實作了ViewParent,但是并沒有繼承view,這個需要特別注意,所以我們在使用view.getParent函式不可輕易強轉為ViewGroup/View等.

在執行繪制操作的時候會回呼ViewRootImpl.performTraversals.但是這個函式特別冗長,網上許多原始碼分析對函式進行一些精簡,但是這樣會丟失一些優化邏輯,比如是否要執行performDraw等判斷.

class ViewRootImpl{
  private void performTraversals() {

     //--------------------------[performMeasure]--------------------
      boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
              (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
      //根據一些情況執行performMeasure
      if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
              || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {

          int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
          int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

           //執行測量
          performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
      }
      //--------------------------[performLayout]--------------------
      final boolean didLayout = layoutRequested && !mStopped;
        
      boolean triggerGlobalLayoutListener = didLayout|| mAttachInfo.mRecomputeGlobalAttributes;
      //根據一些情況執行
      if (didLayout) {
          //執行布局
          performLayout(lp, desiredWindowWidth, desiredWindowHeight);
      }


      //--------------------------[performDraw]--------------------    
        boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() ||viewVisibility != View.VISIBLE;
//根據一些情況執行
      if (!cancelDraw && !newSurface) {
          if (!skipDraw || mReportNextDraw) {
              //執行繪制
              performDraw();
          }
      }
  }
}

上面不需要只需要理解執行繪制不一定會完整執行performMeasure,performLayout,performDraw這三個函式.

不過為了方便我理解大體流程我們先忽略這些優化,于是乎上面的代碼簡化為:

//ViewRootImpl.java
class ViewRootImpl{
  private void performTraversals() {

      //--------------------------[performMeasure]--------------------
       //執行測量
       int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
       int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
       
       performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
      //--------------------------[performLayout]--------------------
      //執行布局
      performLayout(lp, desiredWindowWidth, desiredWindowHeight);

      //--------------------------[performDraw]--------------------    
      //執行測繪
      performDraw();
     
  }
}

performMeasure

在了解測量布局之前我們需要一些前置知識MeasureSpec.

MeasureSpec

假設我們想用一個Int型別標識兩種不同事物怎么辦?
我們就會用將32位bit進行拆分使用,比如前2位作為一個變數使用,后30位bit作為另一個變數使用.這樣我們就可以減少一個變數的創建,但是卻要求程式員用位操作取出對應資料.
在這里插入圖片描述
這種思想被放入View的寬高設定上

    <!--  寬高是為由子view自行確定型別 -->
    <View
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <!--  寬高是填充父布局大小,這某種意義上它的寬高是確定值型別  -->
    <View
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
    <!--  寬高是確定值型別  -->
    <View
        android:layout_width="35dp"
        android:layout_height="35dp" />

我們這里有一個小需求就是用一個int變數去表示android:layout_width是哪種型別的,并且這個型別的長度,比如:
android:layout_width="35dp"我們將寫死寬度的視為EXACTLY型別,然后在放入35dp在一個變數中.
在這里插入圖片描述

MeasureSpec就是幫我們做了這樣事情
我們細看其類宣告

//View.java
public static class MeasureSpec {
		//用于移動位,這里第30位作為大小,前兩位作為模式mode
        private static final int MODE_SHIFT = 30;
        //掩碼 用于獲得當前是哪一種模式
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;

        /**
         * 父view并不限制子view的大小,子view可以無條件無限制宣告自己所需的大小
         */
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        /**
        * 父view已經有明確的大小給子view去使用,如果子view是EXACTLY,你只能使用小于等于父給定范圍
        */
        public static final int EXACTLY  = 1 << MODE_SHIFT;

        /**
         * 父view不約子view大小,但是不能超過父view 的范圍
         */
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        //根據傳入的大小和模式構造一個符合MeasureSpec的變數
        public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

     	//得到當前變數的模式
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }

       //當前變數所設定的大小
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
		//調整measureSpec內部的大小,內部的大小加上delta然后回傳
		//你可以傳一個負數減少大小哦
        static int adjust(int measureSpec, int delta) {
            final int mode = getMode(measureSpec);
            if (mode == UNSPECIFIED) {
                // No need to adjust size for UNSPECIFIED mode.
                return makeMeasureSpec(0, UNSPECIFIED);
            }
            int size = getSize(measureSpec) + delta;
            if (size < 0) {
             
                size = 0;
            }
            return makeMeasureSpec(size, mode);
        }

       	//回傳一個供閱讀的字串,方便除錯
        public static String toString(int measureSpec) {
            int mode = getMode(measureSpec);
            int size = getSize(measureSpec);

            StringBuilder sb = new StringBuilder("MeasureSpec: ");

            if (mode == UNSPECIFIED)
                sb.append("UNSPECIFIED ");
            else if (mode == EXACTLY)
                sb.append("EXACTLY ");
            else if (mode == AT_MOST)
                sb.append("AT_MOST ");
            else
                sb.append(mode).append(" ");

            sb.append(size);
            return sb.toString();
        }
    }

測量大小原始碼分析

當我們理解MeasureSpec后在回頭看下原始碼

//ViewRootImpl.java
class ViewRootImpl{
  private void performTraversals() {

      //--------------------------[performMeasure]--------------------
       //執行測量,mWidth可以理解為windows視窗的大小,多數情況為扣除狀態欄的螢屏大小
       //lp.width 一般都為ViewGroup.LayoutParams.MATCH_PARENT
       //getRootMeasureSpec回傳符合MeasureSpec規范的尺寸變數
       int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
       int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
       
       //我們重點關心這個函式
       performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  }
  //比較簡單讀者可以自己看下
  private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
        case ViewGroup.LayoutParams.MATCH_PARENT:
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
       	//略
        }
        return measureSpec;
    }
	 
	 private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
		//mView是Decorview
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
       
    }
}

回過頭我們看下Decorviewmeasure函式

//PhoneWindow.java
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {

}

由于DecorView繼承FrameLayout,而measure函式位于View.java中且不可以重寫,所以我們直接看View.java

//View.java
class View{
	public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
				//onMeasure一般由子類重寫,這里我們看看默認view的實作
                onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    
   protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
}

為了簡化分析流程我們先分析getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec)在分析setMeasuredDimension

class View{
    private int mMinWidth;
    protected int getSuggestedMinimumWidth() {
    	//如果有背景就取背景大小和mMinWidth的最大值,這里可以理解為回傳了0
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }
	//size可以理解為0
	//measureSpec是父親傳下來
	 public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);
		//從getRootMeasureSpec可以得知specMode為EXACTLY
        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
               
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
        	//specSize就是父布局的大小,也就是除了狀態欄以外的大小(全屏或者視窗等除外)
            result = specSize;
            break;
        }
        return result;
    }
}

最后我們回到setMeasuredDimension函式

//View.java
class View{
  protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        setMeasuredDimensionRaw(measuredWidth, measuredHeight);
    }
    
    int mMeasuredWidth;
    int mMeasuredHeight;
    
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    	//最終是將measuredWidth和measuredHeight保存到自身屬性中
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
}

我們看完默認的onMeasure實作,我們最后看下DecorView的實作

//PhoneWindow.java
class DecorView{
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            final DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
            //當前是否為豎屏
            final boolean isPortrait = metrics.widthPixels < metrics.heightPixels;

            final int widthMode = getMode(widthMeasureSpec);
            final int heightMode = getMode(heightMeasureSpec);

            boolean fixedWidth = false;

            if (widthMode == AT_MOST) {

                //橫豎屏處理,適當的修改widthMeasureSpec

            }

            if (heightMode == AT_MOST) {
               //橫豎屏處理,適當的修改 heightMeasureSpec
            }

            //呼叫父類的函式也就是FrameLayout
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);

            //略

           
        }
}

既然呼叫了父類的onMeasure我們跟入FrameLayout

//FrameLayout.java
   class FrameLayout{

        private final ArrayList<View> mMatchParentChildren = new ArrayList<View>(1);

        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

            int count = getChildCount();

            //如果父布局傳傳入的widthMeasureSpec 和heightMeasureSpec都不是EXACTLY回傳true
            //這里decorview傳入的都是EXACTLY,所以這里為true
            final boolean measureMatchParentChildren =
                    MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                    MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
            //這個集合,用于存放要進行測繪的子view
            mMatchParentChildren.clear();

            int maxHeight = 0;
            int maxWidth = 0;
            int childState = 0;

            //遍歷所有子view
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                 //如果mMeasureAllChildren為true或者child可見時,子view會被進行測繪
                if (mMeasureAllChildren || child.getVisibility() != GONE) {

                    //略
                    //將子view添加都集合中
                    mMatchParentChildren.add(child);
                    //略
                   
                }
            }

            //略....

            //設定自己高度,這里是 FrameLayout相關邏輯,我們不必進行深入   
            setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                    resolveSizeAndState(maxHeight, heightMeasureSpec,
                            childState << MEASURED_HEIGHT_STATE_SHIFT));

            count = mMatchParentChildren.size();
            //取出所有子view遍歷
            if (count > 1) {
                for (int i = 0; i < count; i++) {
                    
                    final View child = mMatchParentChildren.get(i);

                    //得到布局引數
                    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
                   
                    int childWidthMeasureSpec;
                    int childHeightMeasureSpec;
                    
                    //子view想跟FrameLayout一樣大
                    if (lp.width == LayoutParams.MATCH_PARENT) {
                        //這里減去自己左右pading和子view的margin然后設定
                        childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth() -
                                getPaddingLeftWithForeground() - getPaddingRightWithForeground() -
                                lp.leftMargin - lp.rightMargin,
                                MeasureSpec.EXACTLY);
                    } else {
						//getChildMeasureSpec是一個非常重要的函式
						//根據自身的MeasureSpec和子view的LayoutParams生成一個對應MeasureSpec給子view進行測量
                        childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                                getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                                lp.leftMargin + lp.rightMargin,
                                lp.width);
                    }
                    //同上
                    if (lp.height == LayoutParams.MATCH_PARENT) {
                        childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(getMeasuredHeight() -
                                getPaddingTopWithForeground() - getPaddingBottomWithForeground() -
                                lp.topMargin - lp.bottomMargin,
                                MeasureSpec.EXACTLY);
                    } else {
                        childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
                                getPaddingTopWithForeground() + getPaddingBottomWithForeground() +
                                lp.topMargin + lp.bottomMargin,
                                lp.height);
                    }
                    //最后呼叫子viewmeasure
                    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
                }
            }
        }
    }

//ViewGroup.java
class ViewGroup{
	//引數padding: 如果是測量寬,那么padding應該為 左pading+右pading+子view左邊距+子view右邊距
	//也就是說pading是不可用的寬度
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);
		//size為當前viewGroup最大可用寬/高
		//specSize - padding減去 pading和子view邊距
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        //當前ViewGroup的布局寬高是明確的 
        case MeasureSpec.EXACTLY:
        	//如果子view的android:width或者android:height是一個明確的數值
        	//比如android:width="35dp"
            if (childDimension >= 0) {
            	//設定子view的 寬/高 度
                resultSize = childDimension;
                //模式為EXACTLY
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 如果子view 想和當前viewGrou高度一致
                //設定父布局的寬高度
                resultSize = size;
                //模式為EXACTLY
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //子view想自行決定寬高
                //那么寬高為自身ViewGroup的上限寬高 
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        //當前ViewGroup的父布局讓其自行決定大小,但是不能超過父親
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
            	//子view有一個明確的大小
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //子view想跟ViewGroup一樣大,但是ViewGroup不是固定的
                //所以約束子view不超過父布局即可
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 子view想自行決定大小,但是不能超過父view
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // ViewGroup父布局不限制子view寬高,
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //由于ViewGroup寬高是不受限的,而子view也想和我們一樣.
                //所以這里會下沉UNSPECIFIED
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
              //由于ViewGroup寬高是不受限的,所以不限制子view所想要的高度.
              //所以這里會下沉UNSPECIFIED
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //利用上面的大小和模式構造變數回傳
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }
}

當理解getChildMeasureSpec函式后對于UNSPECIFIED,AT_MOST,EXACTLY這個三個模式的理解應該很深刻.

我們最后做一下performMeasure的總結:
Decorview首先會呼叫measure函式,在呼叫onMeasure.
最后是給自己設定寬高資訊到 mMeasuredWidthmMeasuredHeight,并計算出子viewMeasureSpec,然后呼叫子view的measure函式.

performLayout

//ViewRootImpl.java
    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;
        
       //Decorview繼承FramLayout,而FramLayout繼承View
		//我們直接看view的實作
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

        mInLayout = false;
    }

//View.java
class View{
	 public void layout(int l, int t, int r, int b) {



        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
		//給自己mLeft mTop mBottom mRight 設定
		//并且根據情況呼叫onsizechange函式
        boolean changed =  setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            //呼叫onlayout函式,這個一般由子類實作,由于decorview繼承FramLayout,所以我們待會看著類即可
            onLayout(changed, l, t, r, b);

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            //回呼函式,您可以注冊一個函式拿到view的寬高哦    
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
    }
	protected int mLeft;
	protected int mRight;
	protected int mTop;
	protected int mBottom;

	protected boolean setFrame(int left, int top, int right, int bottom) {
	    
	    boolean changed = false;
	    //mLeft mRight  mTop mBottom還未初始化所以都是0
	    //所以這里自然回傳true進入if中
	
	    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
	        changed = true;
	
	        // Remember our drawn bit
	        int drawn = mPrivateFlags & PFLAG_DRAWN;
	
	        int oldWidth = mRight - mLeft;
	        int oldHeight = mBottom - mTop;
	        int newWidth = right - left;
	        int newHeight = bottom - top;
	
	        //很明顯不一樣,sizeChanged為true
	        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
	
	        //標記快取視圖失效,因為寬高改變
	        invalidate(sizeChanged);
	
	
	        //初始化變數
	        mLeft = left;
	        mTop = top;
	        mRight = right;
	        mBottom = bottom;
	
	        //設定這個視圖繪制的位置,這個函式會呼叫native函式,這里我們可以不用關心
	        //只需要知道這個view超過這個范圍是無法繪制的
	        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
	
	        mPrivateFlags |= PFLAG_HAS_BOUNDS;
	
	
	        //如果大小改變呼叫自己sizeChange函式,而sizeChange又會呼叫onSizeChanged
	        if (sizeChanged) {
	            sizeChange(newWidth, newHeight, oldWidth, oldHeight);
	        }
	
	        //如果視圖可見那么進行重繪,
	        if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
	          
	            mPrivateFlags |= PFLAG_DRAWN;
	            invalidate(sizeChanged);
	            invalidateParentCaches();
	        }
	
	        // Reset drawn bit to original value (invalidate turns it off)
	        mPrivateFlags |= drawn;
	
	        mBackgroundSizeChanged = true;
	
	        notifySubtreeAccessibilityStateChangedIfNeeded();
	    }
	    return changed;
	}
}


我們最后看FramLayoutOnlayout函式

class FramLayout{


    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

    void layoutChildren(int left, int top, int right, int bottom,
                                  boolean forceLeftGravity) {

        final int count = getChildCount();

        final int parentLeft = getPaddingLeftWithForeground();

        final int parentRight = right - left - getPaddingRightWithForeground();

        final int parentTop = getPaddingTopWithForeground();

        final int parentBottom = bottom - top - getPaddingBottomWithForeground();

        mForegroundBoundsChanged = true;

        //遍歷所有子view 然后根據子view的gravity和自身的padding確定view的擺放位置
        for (int i = 0; i < count; i++) {
            
            final View child = getChildAt(i);
            

            //視圖可見在進行操作
            if (child.getVisibility() != GONE) {


                /*
                * 根據布局引數和自身padding數值等確定子view的位置
                * 然后呼叫子view的layout
                */
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();

                final int width = child.getMeasuredWidth();
                final int height = child.getMeasuredHeight();

                int childLeft;
                int childTop;

                int gravity = lp.gravity;
                if (gravity == -1) {
                    gravity = DEFAULT_CHILD_GRAVITY;
                }

                final int layoutDirection = getLayoutDirection();
                final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }

                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }

                //最終呼叫子view的layout函式    
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
    }

}

總結:
performLayout其實就是設定mLeft mTop mRight mBottom;,這個幾個數值由父親根據自身的一些特點決定.
對比performMeasure僅僅用來宣告想要的大小,而最終決定大小的還是performLayout函式.

performDraw

//ViewRootImpl.java
class ViewRootImpl{
 	private void performDraw() {
        draw(fullRedrawNeeded);
    }
    
    private void draw(boolean fullRedrawNeeded) {
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) 
            return;
              
    }
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {

       
        final Canvas canvas;
    
        final int left = dirty.left;
        final int top = dirty.top;
        final int right = dirty.right;
        final int bottom = dirty.bottom;
        
        //dirty表示繪制區域,如果傳入null將繪制整個surface
        canvas = mSurface.lockCanvas(dirty);

         //最終呼叫到decorview的draw函式,這里會呼叫view的draw函式
        mView.draw(canvas);
        
        surface.unlockCanvasAndPost(canvas);
       
        return true;
    }
}

ViewDraw被寫到爛了,不過為了文章完整性再寫一遍,其實注釋寫的很好

class View{
	public void draw(Canvas canvas) {
	    
        final int privateFlags = mPrivateFlags;
        final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;

        /*
         * 繪制執行若干個步驟,每個步驟根據適當順序執行
         *  
         * Draw traversal performs several drawing steps which must be executed
         * in the appropriate order:
         *	    1.繪制背景
         *      1. Draw the background
         *      
         * 		2. 如果有必要的話,就保存canvas的圖層用于繪制過度邊緣滾動/拉扯效果
         * 		2. If necessary, save the canvas' layers to prepare for fading
         * 		
         * 		3.繪制view 的內容
         *      3. Draw view's content
         *      
         * 		4. 繪制子view
         * 		4. Draw children
         * 		
         * 		5.如果有必要的話,繪制過度邊緣滾動/拉扯效果然后恢復canvas圖層
         *      5. If necessary, draw the fading edges and restore layers
         *      
         * 		6.繪制一些裝飾內容 如滾動條
         * 		6. Draw decorations (scrollbars for instance)
         */

        // Step 1, draw the background, if needed
        // 第一步 繪制背景色
        int saveCount;
        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        //不需要繪制過度滾動/拉扯 效果
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            // 第3步 繪制view的內容
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            //第四步 繪制子view
            dispatchDraw(canvas);

            // Step 6, draw decorations (scrollbars)
            //第六步 繪制滾動條
            onDrawScrollBars(canvas);

            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // 回傳
            return;
        }

        /* 
         * 下面是一個是一個完整繪制流程,因為不常用所以我們這里不分析
         * 
         * Here we do the full fledged routine...
         * (this is an uncommon case where speed matters less,
         * this is why we repeat some of the tests that have been
         * done above)
         */

        boolean drawTop = false;
        boolean drawBottom = false;
        boolean drawLeft = false;
        boolean drawRight = false;

        float topFadeStrength = 0.0f;
        float bottomFadeStrength = 0.0f;
        float leftFadeStrength = 0.0f;
        float rightFadeStrength = 0.0f;

        // Step 2, save the canvas' layers
        int paddingLeft = mPaddingLeft;

        final boolean offsetRequired = isPaddingOffsetRequired();
        if (offsetRequired) {
            paddingLeft += getLeftPaddingOffset();
        }

        int left = mScrollX + paddingLeft;
        int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
        int top = mScrollY + getFadeTop(offsetRequired);
        int bottom = top + getFadeHeight(offsetRequired);

        if (offsetRequired) {
            right += getRightPaddingOffset();
            bottom += getBottomPaddingOffset();
        }

        final ScrollabilityCache scrollabilityCache = mScrollCache;
        final float fadeHeight = scrollabilityCache.fadingEdgeLength;
        int length = (int) fadeHeight;

        // clip the fade length if top and bottom fades overlap
        // overlapping fades produce odd-looking artifacts
        if (verticalEdges && (top + length > bottom - length)) {
            length = (bottom - top) / 2;
        }

        // also clip horizontal fades if necessary
        if (horizontalEdges && (left + length > right - length)) {
            length = (right - left) / 2;
        }

        if (verticalEdges) {
            topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
            drawTop = topFadeStrength * fadeHeight > 1.0f;
            bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
            drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
        }

        if (horizontalEdges) {
            leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
            drawLeft = leftFadeStrength * fadeHeight > 1.0f;
            rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
            drawRight = rightFadeStrength * fadeHeight > 1.0f;
        }

        saveCount = canvas.getSaveCount();

        int solidColor = getSolidColor();
        if (solidColor == 0) {
            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }

            if (drawBottom) {
                canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
            }

            if (drawLeft) {
                canvas.saveLayer(left, top, left + length, bottom, null, flags);
            }

            if (drawRight) {
                canvas.saveLayer(right - length, top, right, bottom, null, flags);
            }
        } else {
            scrollabilityCache.setFadeColor(solidColor);
        }

        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

        // Step 4, draw the children
        dispatchDraw(canvas);

        // Step 5, draw the fade effect and restore layers
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;

        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, right, top + length, p);
        }

        if (drawBottom) {
            matrix.setScale(1, fadeHeight * bottomFadeStrength);
            matrix.postRotate(180);
            matrix.postTranslate(left, bottom);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, bottom - length, right, bottom, p);
        }

        if (drawLeft) {
            matrix.setScale(1, fadeHeight * leftFadeStrength);
            matrix.postRotate(-90);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, left + length, bottom, p);
        }

        if (drawRight) {
            matrix.setScale(1, fadeHeight * rightFadeStrength);
            matrix.postRotate(90);
            matrix.postTranslate(right, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(right - length, top, right, bottom, p);
        }

        canvas.restoreToCount(saveCount);

        // Step 6, draw decorations (scrollbars)
        onDrawScrollBars(canvas);

        if (mOverlay != null && !mOverlay.isEmpty()) {
            mOverlay.getOverlayView().dispatchDraw(canvas);
        }
    }

}

上面的代碼我只需要關注幾個函式的呼叫
onDraw,dispatchDraw,onDrawScrollBars這個幾個函式根據自己的業務需求進行重寫,
onDraw由子view實作,dispatchDraw一般由viewGroup重寫

class ViewGroup{
	    protected void dispatchDraw(Canvas canvas) {

        boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
        int flags = mGroupFlags;

        if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {
            final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;

            final boolean buildCache = !isHardwareAccelerated();
            //如果沒有開啟硬體加速那么如果存在快取時,利用快取繪制子view,內部利用bitmap存盤視圖
            //這里沒必要深究
            for (int i = 0; i < childrenCount; i++) {
                final View child = children[i];
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                    final LayoutParams params = child.getLayoutParams();
                    attachLayoutAnimationParameters(child, params, i, childrenCount);
                    bindLayoutAnimation(child);
                    if (cache) {
                        child.setDrawingCacheEnabled(true);
                        if (buildCache) {
                            child.buildDrawingCache(true);
                        }
                    }
                }
            }
        }


        int clipSaveCount = 0;
        final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
        //裁剪canvas畫布給子view用,不讓子view繪制約束區域.這里是扣除自身的padding大小和滾動距離
        if (clipToPadding) {
            clipSaveCount = canvas.save();
            canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                    mScrollX + mRight - mLeft - mPaddingRight,
                    mScrollY + mBottom - mTop - mPaddingBottom);
        }

        // We will draw our child's animation, let's reset the flag
        mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
        mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;

        boolean more = false;
        final long drawingTime = getDrawingTime();

        if (usingRenderNodeProperties) canvas.insertReorderBarrier();
     
        final ArrayList<View> preorderedList = usingRenderNodeProperties
                ? null : buildOrderedChildList();

        final boolean customOrder = preorderedList == null
                && isChildrenDrawingOrderEnabled();

         //遍歷子view然后通過drawChild讓子view執行繪制       
        for (int i = 0; i < childrenCount; i++) {
        
            int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
        
            final View child = (preorderedList == null)
                    ? children[childIndex] : preorderedList.get(childIndex);
            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
            	//我們跟進去看看實作
                more |= drawChild(canvas, child, drawingTime);
            }
        }



        if (preorderedList != null) preorderedList.clear();

        // 繪制一些消失影片,這里完全不需要關心
        if (mDisappearingChildren != null) {
            final ArrayList<View> disappearingChildren = mDisappearingChildren;
            final int disappearingCount = disappearingChildren.size() - 1;
            // Go backwards -- we may delete as animations finish
            for (int i = disappearingCount; i >= 0; i--) {
                final View child = disappearingChildren.get(i);
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        if (usingRenderNodeProperties) canvas.insertInorderBarrier();

        //恢復圖層
       
        if (clipToPadding) {
            //略
            canvas.restoreToCount(clipSaveCount);
        }
		//略
    }
	
}

我們看下上面drawChild(canvas, child, drawingTime);

class ViewGroup{
  protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }
}
class View{
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
      


        boolean usingRenderNodeProperties = mAttachInfo != null && mAttachInfo.mHardwareAccelerated;
       

        if (!usingRenderNodeProperties) {
            //裁剪子view 關于android:clipChildren用法網上有很多.如果不設定默認為true
            //從里我們可以知道子view的canvas畫布大小為什么受限
            if ((flags & ViewGroup.FLAG_CLIP_CHILDREN) == ViewGroup.FLAG_CLIP_CHILDREN
                    && cache == null) {

                if (offsetForScroll) {
                    canvas.clipRect(sx, sy, sx + (mRight - mLeft), sy + (mBottom - mTop));
                } else {
                    if (!scalingRequired || cache == null) {
                        //裁剪到父布局定義的寬高
                        canvas.clipRect(0, 0, mRight - mLeft, mBottom - mTop);
                    } else {
                        canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
                    }
                }
            }

            if (mClipBounds != null) {
                canvas.clipRect(mClipBounds);
            }
        }

      
 		if (usingRenderNodeProperties) {
 			//函式內部會觸發computeScroll函式
            renderNode = getDisplayList();  
        }
        if (hasNoCache) {
           
            if (!layerRendered) {
                if (!hasDisplayList) {
                    //根據flag判斷是否要跳過繪制
                    //如果跳過那么直接分發繪制到子view
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                        dispatchDraw(canvas);
                        if (mOverlay != null && !mOverlay.isEmpty()) {
                            mOverlay.getOverlayView().draw(canvas);
                        }
                    } else {
                         //繪制內容   
                        draw(canvas);
                    }
                }                
            }
        } else if (cache != null) {
           //略
        }

   

        return more;
    }

}
//view.java
 public RenderNode getDisplayList() {
        updateDisplayListIfDirty();
        return mRenderNode;
    }
  private void updateDisplayListIfDirty() {
  	   //方法中進行滾動相關操作
       computeScroll();
   }

上面便是整個繪制流程

setWillNotDraw

我們在很多時候ViewGroup是不需要繪制也就是不需要呼叫onDraw函式.對于此google提供了
setWillNotDraw函式.

//View.java
class View{
 //傳入true時,將不會回呼onDraw函式
 public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
 }

    void setFlags(int flags, int mask) {
		//略...
		//設定一個標志位
		mPrivateFlags |= PFLAG_SKIP_DRAW;
	}
    
}

回顧我們view的繪制分發流程,

draw(Canvas canvas, ViewGroup parent, long drawingTime)內部會呼叫draw(Canvas canvas).

class View{
	 boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
	 	  			//根據flag判斷是否要跳過繪制
                    //如果跳過那么直接分發繪制到子view
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                        dispatchDraw(canvas);
                        if (mOverlay != null && !mOverlay.isEmpty()) {
                            mOverlay.getOverlayView().draw(canvas);
                        }
                    } else {
                         //繪制內容   
                        draw(canvas);
                    }
	 }
}

可以看到draw函式判斷PFLAG_SKIP_DRAW在進行函式跳轉,如果設定了跳過draw函式,而后直接掉用dispatchDraw(canvas);進行繪制事件下沉

默認情況ViewGroup建構式中設定了不需要進行繪制

class ViewGroup{
	  public ViewGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initViewGroup();
        initFromAttributes(context, attrs, defStyleAttr, defStyleRes);
    }
      private void initViewGroup() {
        // ViewGroup doesn't draw by default
        if (!debugDraw()) {
            setFlags(WILL_NOT_DRAW, DRAW_MASK);
        }
     }
}

多次invalidate會怎么樣

本來想寫的后來發現發現一篇挺好的

每日一問 | View invalidate() 相關的一些細節探究~

參考

Android View的繪制流程

面試官帶你學安卓 - 從 View 的繪制流程說起

Android應用層View繪制流程與原始碼分析

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/277354.html

標籤:其他

上一篇:帶你解決80%的iOS開發難題

下一篇:Android開發知識(理論篇)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more