文章目錄
- 前言
- 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在oncreate和onresume回呼的時候根本沒有走繪制流程,所以我們經常遇到獲取高度為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);
}
}
回過頭我們看下Decorview的measure函式
//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.
最后是給自己設定寬高資訊到 mMeasuredWidth和mMeasuredHeight,并計算出子view的MeasureSpec,然后呼叫子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;
}
}
我們最后看FramLayout的Onlayout函式
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;
}
}
View的Draw被寫到爛了,不過為了文章完整性再寫一遍,其實注釋寫的很好
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開發知識(理論篇)
