一斤寇、目錄
二感论、簡介
在理解View繪制流程焚鲜,之前我們先解析下View繪制中的幾大重要成員。
-
Window
- 介紹:Window表示一個窗口雀费,用來承載View的抽象類干奢。不管是Activity、Dialog還是Toast,它們的視圖實際上都是附加在Window上的坐儿,因此Window實際是View的直接管理者律胀。他的唯一實現(xiàn)是PhoneWindow
image -
WindowManager
- 簡介:WindowManager繼承自ViewManager的接口宋光。WindowManager主要用來管理窗口的一些狀態(tài)貌矿、屬性、view增加罪佳、刪除、更新、窗口順序第股、消息收集和處理等。
image -
ViewRootImpl
- 簡介:ViewRootImpl是View中的最高層級克握,屬于所有View的根(但ViewRootImpl不是View,只是實現(xiàn)了ViewParent接口)枷踏,是實現(xiàn) View 的繪制的類菩暗,它實現(xiàn)了View和WindowManager之間的通信協(xié)議。大多數(shù)情況下旭蠕,他也是WindowManagerGloable的內(nèi)部功能具體實現(xiàn)
-
DecorView
- DecorView是Activity內(nèi)所有View的父布局停团,DecorView 是 FrameLayout 的子類,F(xiàn)rameLayout 是 GroupView 的子類掏熬,所以DecorView 是一個 GroupView佑稠。內(nèi)部包含上面是標(biāo)題,下面是內(nèi)容欄(id是content)
三旗芬、Activity舌胶、PhoneWindow、DecorView三者之間的關(guān)系
每一個Acyiviy 包含一個Window對象疮丛,這個window對象是phoneWindow
PhoneWindow內(nèi)包含一個DecorView幔嫂。同時,PhoneWindow也是Activity和View系統(tǒng)交互的接口誊薄。DecorView是整個窗口內(nèi)的所有View的跟布局
DecorView將屏幕劃分兩個區(qū)域:1.titleView 2.ContentView
DecorView本質(zhì)上是一個FrameLayout婉烟,是Activity中所有View的祖先
而我們平時所寫的就是展示在ContentView中的,下圖表示Activity的構(gòu)成暇屋。
四似袁、DecorView的加載
從Activity的startActivity開始,最終調(diào)用到ActivityThread的handleLaunchActivity方法來創(chuàng)建Activity咐刨,相關(guān)核心代碼如下:
- handleLaunchActivity
handleLaunchActivity的主要操作
1昙衅、初始化WindowManagerService
2、實例化Activity定鸟、初始化Activity的Window等參數(shù)而涉、回調(diào)Activity的onCreate()
3、執(zhí)行Acttivity的onResume()
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...略
// 初始化WindowManagerService
WindowManagerGlobal.initialize();
//實例化Activity联予,初始化Activity的Window等參數(shù)并回調(diào)Activity的onCreate啼县、onStart
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
//執(zhí)行Activity的onResume
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
...略
} else {
...略
}
}
- performLaunchActivity
performLaunchActivity的操作步驟:
1、獲取Activity信息
2沸久、通過反射獲取Activity的類季眷,并創(chuàng)建Activity
4、獲取Application
5卷胯、初始化Activity的成員變量包括Window子刮、Application、和mainThread等窑睁。初始化PhoneWindow的構(gòu)造函數(shù)的內(nèi)部初始化了PhoneWindow的DecorView挺峡。
6葵孤、回調(diào)Activity的onCreate方法
通常Activity的onCreate的內(nèi)我們會調(diào)用setContentView
而setContentView 內(nèi)部調(diào)用PhoneWindow的setContentView,
PhoneWindow的setContentView通過LayoutInflater.inflate()將我們的布局加載到DecorView中
7橱赠、如果Activity沒有Finish則調(diào)用Activity的onStart()
8尤仍、設(shè)置Activity的savedInstanceState
9、設(shè)置Window的title
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//1狭姨、獲取Activity信息
ActivityInfo aInfo = r.activityInfo;
if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
Context.CONTEXT_INCLUDE_CODE);
}
...略
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
//2吓著、通過反射獲取到activity類,并創(chuàng)建Activity實例
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
//3、如果沒在清單里注冊就會出現(xiàn)這個錯誤
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
try {
//4送挑、獲取Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
...略
appContext.setOuterContext(activity);
//5绑莺、初始化Activity的成員變量包括Window、Application惕耕、和mainThread等
// 初始化PhoneWindow的構(gòu)造函數(shù)的內(nèi)部初始化了PhoneWindow的DecorView纺裁。
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
if (customIntent != null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
checkAndBlockForNetworkAccess();
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if (theme != 0) {
activity.setTheme(theme);
}
activity.mCalled = false;
//6、回調(diào)Activity的onCreate方法
// 通常Activity的onCreate的內(nèi)我們會調(diào)用setContentView
// 而setContentView 內(nèi)部調(diào)用PhoneWindow的setContentView司澎,
// PhoneWindow的setContentView通過LayoutInflater.inflate()將我們的布局加載到DecorView中
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
r.activity = activity;
r.stopped = true;
//7欺缘、如果Activity沒有Finish則調(diào)用Activity的onStart()
if (!r.activity.mFinished) {
activity.performStart();
r.stopped = false;
}
// 8、設(shè)置Activity的savedInstanceState
if (!r.activity.mFinished) {
if (r.isPersistable()) {
if (r.state != null || r.persistentState != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
r.persistentState);
}
} else if (r.state != null) {
mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
}
}
//9挤安、設(shè)置Window的title
if (!r.activity.mFinished) {
activity.mCalled = false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnPostCreate(activity, r.state,
r.persistentState);
} else {
mInstrumentation.callActivityOnPostCreate(activity, r.state);
}
if (!activity.mCalled) {
throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onPostCreate()");
}
}
}
r.paused = true;
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to start activity " + component
+ ": " + e.toString(), e);
}
}
return activity;
}
- handleResumeActivity
handleResumeActivity操作步驟:
1谚殊、執(zhí)行Activity的onResume
2、調(diào)用 WindowManager的addView(decor, l);
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
unscheduleGcIdler();
mSomeActivitiesChanged = true;
// 調(diào)用Activity的onResume方法
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
...
if (r.window == null &&& !a.mFinished && willBeVisible) {
//獲取Activity的Window
r.window = r.activity.getWindow();
//獲取Window的DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//獲取Activity的WindowManagerImpl
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
//將Window的DecorView賦值個Activity的mDecor
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
// WindowManager的實現(xiàn)類是WindowManagerImpl蛤铜,
// 所以實際調(diào)用的是WindowManagerImpl的addView方法
wm.addView(decor, l);
}
}
}
}
- WindowManager的addView(View view, ViewGroup.LayoutParams params)
實際上WindowManager的實現(xiàn)類時WindowManagerImpl嫩絮,而WindowManagerImpl的addView()方法內(nèi)部,調(diào)用了WindowManagerGlobal的addView围肥。所以接下來分析WindowManagerGlobal的addView剿干。源碼如下:
// WindowManagerGlobal的addView方法
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
...
ViewRootImpl root;
View pannelParentView = null;
synchronized (mLock) {
...
// 創(chuàng)建ViewRootImpl實例
root = new ViewRootImpl(view..getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
try {
// 把DecorView加載到Window中,觸發(fā)View的測量穆刻、布局置尔、繪制
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
// ViewRootImpl的方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
...
requestLayout();
...
}
}
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
...
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
...
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
...
performTraversals();
...
}
}
最終ViewRootImpl的setView方法利用執(zhí)行Runable的方式執(zhí)行了performTraversals();
- DecorView加載程序流執(zhí)行程圖
五、View繪制整體流程
View的繪制最終是從ViewRootImpl的performTraversals()方法開始氢伟,從上到下遍歷整個視圖樹榜轿,每個View控件負(fù)責(zé)繪制自己,而ViewGroup還需要負(fù)責(zé)通知自己的子View進(jìn)行繪制操作朵锣。ViewRootImpl的performTraversals()方法的主要源碼如下:
private void performTraversals() {
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
//執(zhí)行測量流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//執(zhí)行布局流程
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
//執(zhí)行繪制流程
performDraw();
}
流程圖如下:
- 注:preformLayout和performDraw的傳遞流程和performMeasure是類似的谬盐,唯一不同的是,performDraw的傳遞過程是在draw方法中通過dispatchDraw來實現(xiàn)的猪勇,不過這并沒有本質(zhì)區(qū)別设褐。
- 獲取content:
ViewGroup content = (ViewGroup)findViewById(android.R.id.content);
六、MeasureSpec
MeasureSpec是View類的一個靜態(tài)內(nèi)部類泣刹,用來說明應(yīng)該如何測量這個View助析。MeasureSpec代表一個32位的int值,前倆位代表SpecMode椅您,后30位代表SpecSize.
其中:SpecMode代表測量的模式外冀,SpecSize值在某種測量模式下的規(guī)格大小。
-
三個測量模式說明
EXACTLY:英文含義是精確的掀泳。精確測量模式雪隧,這時候View的大小是SpecSize的大小。對應(yīng)布局參數(shù)是 MATCH_PARENT或精確值大小
AT_MOST:英文含義是至多员舵。最大值測量模式脑沿,View的尺寸是不超過父布局最大尺寸的任意尺寸。對應(yīng)布局參數(shù)wrap_content马僻。
UNSPECIFIED:英文含義未指定的庄拇。不指定測量模式,父視圖沒有限制子視圖的大小韭邓,子視圖可以是想要的任何尺寸措近,通常用于系統(tǒng)內(nèi)部,應(yīng)用開發(fā)中很少用到女淑。
MeasureSpec源碼
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0X3 << MODE_SHIFT;
//三種測量模式
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
// 根據(jù)指定的大小和模式創(chuàng)建一個MeasureSpec
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//當(dāng) mode == UNSPECIFIED 時返回0的MeasureSpec瞭郑,內(nèi)部調(diào)用makeMeasureSpec,
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
//獲取測量模式
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
獲取測量大小
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
// 微調(diào)某個MeasureSpec的大小
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);
}
}
- DecorView的MeasureSpec的創(chuàng)建
DecorView的MeasureSpec的創(chuàng)建是在ViewRootImpl在測量DecorView之前獲取的的鸭你。獲取源碼如下:
performTraversals(){
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
}
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
總結(jié):DecorView的MeasureSpec是由窗口大小和其自身的LayoutParams共同決定
- 普通View的MeasureSpec的創(chuàng)建
普通View的MeasureSpec的創(chuàng)建是在父View在測量子View時獲取
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//父View獲取自身的測量模式和大小
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//獲取在父View的大小中除了自身padding屈张、margin的大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// 如何父布局大小是精確的大小或者是MATCH_PARENT
case MeasureSpec.EXACTLY:
//如果子View自身的LayoutParam的大小>0
//則子View大大小是子View自身的LayoutParam的大小
//測量模式是EXACTLY
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 如果子View的大小是MATCH_PARENT
//則 子View的大小是父View的除去padding和margin的大小
//測量模式是EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//如果子View的大小是WRAP_CONTENT
//則子View的大小是子View的大小是父View的除去padding和margin的大小
//測量模式是AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// // 如何父布局大小是精確的大小或者是WARP_CONTENT
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
//如果子View自身的LayoutParam的大小>0
//則子View大大小是子View自身的LayoutParam的大小
//測量模式是EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 如果子View的大小是MATCH_PARENT
//則 子View的大小是父View的除去padding和margin的大小
//測量模式是AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//如果子View的大小是WRAP_CONTENT
//則子View的大小是子View的大小是父View的除去padding和margin的大小
//測量模式是AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
總結(jié):對于普通的View,它的MeasureSpec由父視圖的MeasureSpec和其自身的LayoutParams共同決定袱巨。UNSPECIFIED模式主要用于系統(tǒng)內(nèi)部多次Measure的情形袜茧,一般不需關(guān)注。
七瓣窄、View的measure流程
private void perormMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
// 具體的測量操作分發(fā)給ViewGroup
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
// 在ViewGroup中的measureChildren()方法中遍歷測量ViewGroup中所有的View
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
// 當(dāng)View的可見性處于GONE狀態(tài)時笛厦,不對其進(jìn)行測量
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
// 測量某個指定的View
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
// 根據(jù)父容器的MeasureSpec和子View的LayoutParams等信息計算
// 子View的MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// View的measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
// ViewGroup沒有定義測量的具體過程,因為ViewGroup是一個
// 抽象類俺夕,其測量過程的onMeasure方法需要各個子類去實現(xiàn)
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
}
// 不同的ViewGroup子類有不同的布局特性裳凸,這導(dǎo)致它們的測量細(xì)節(jié)各不相同,如果需要自定義測量過程劝贸,則子類可以重寫這個方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// setMeasureDimension方法用于設(shè)置View的測量寬高
setMeasureDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
// 如果View沒有重寫onMeasure方法姨谷,則會默認(rèn)調(diào)用getDefaultSize來獲得View的寬高
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = sepcSize;
break;
}
return result;
}
- 對getSuggestMinimumWidth的分析
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinmumWidth());
}
protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
如果View沒有設(shè)置背景,那么返回android:minWidth這個屬性所指定的值映九,這個值可以為0梦湘;如果View設(shè)置了背景,則返回android:minWidth和背景的最小寬度這兩者中的最大值。
-
在Activity中獲取某個View的寬高
由于View的measure過程和Activity的生命周期方法不是同步執(zhí)行的捌议,如果View還沒有測量完畢哼拔,那么獲得的寬/高就是0。所以在onCreate瓣颅、onStart倦逐、onResume中均無法正確得到某個View的寬高信息。解決方式如下:
1宫补、Activity/View#onWindowFocusChanged
// 此時View已經(jīng)初始化完畢 // 當(dāng)Activity的窗口得到焦點和失去焦點時均會被調(diào)用一次 // 如果頻繁地進(jìn)行onResume和onPause檬姥,那么onWindowFocusChanged也會被頻繁地調(diào)用 public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { int width = view.getMeasureWidth(); int height = view.getMeasuredHeight(); } }
2、view.post(runnable)
// 通過post可以將一個runnable投遞到消息隊列的尾部粉怕,// 然后等待Looper調(diào)用此runnable的時候健民,View也已經(jīng)初 // 始化好了 protected void onStart() { super.onStart(); view.post(new Runnable() { @Override public void run() { int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }); }
3、ViewTreeObserver
// 當(dāng)View樹的狀態(tài)發(fā)生改變或者View樹內(nèi)部的View的可見// 性發(fā)生改變時贫贝,onGlobalLayout方法將被回調(diào) protected void onStart() { super.onStart(); ViewTreeObserver observer = view.getViewTreeObserver(); observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @SuppressWarnings("deprecation") @Override public void onGlobalLayout() { view.getViewTreeObserver().removeGlobalOnLayoutListener(this); int width = view.getMeasuredWidth(); int height = view.getMeasuredHeight(); } }); }
4秉犹、View.measure(int widthMeasureSpec, int heightMeasureSpec)
八、View的Layou過程
/ ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
...
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}
// View.java
public void layout(int l, int t, int r, int b) {
...
// 通過setFrame方法來設(shè)定View的四個頂點的位置平酿,即View在父容器中的位置
boolean changed = isLayoutModeOptical(mParent) ?
set OpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
...
onLayout(changed, l, t, r, b);
...
}
// 空方法凤优,子類如果是ViewGroup類型,則重寫這個方法蜈彼,實現(xiàn)ViewGroup
// 中所有View控件布局流程
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
九筑辨、View的Draw過程
private void performDraw() {
...
draw(fullRefrawNeeded);
...
}
private void draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffest, yOffset,
scalingRequired, dirty)) {
return;
}
...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo,
int xoff, int yoff, boolean scallingRequired, Rect dirty) {
...
mView.draw(canvas);
...
}
// 繪制基本上可以分為六個步驟
public void draw(Canvas canvas) {
...
// 步驟一:繪制View的背景
drawBackground(canvas);
...
// 步驟二:如果需要的話,保持canvas的圖層幸逆,為fading做準(zhǔn)備
saveCount = canvas.getSaveCount();
...
canvas.saveLayer(left, top, right, top + length, null, flags);
...
// 步驟三:繪制View的內(nèi)容
onDraw(canvas);
...
// 步驟四:繪制View的子View
dispatchDraw(canvas);
...
// 步驟五:如果需要的話棍辕,繪制View的fading邊緣并恢復(fù)圖層
canvas.drawRect(left, top, right, top + length, p);
...
canvas.restoreToCount(saveCount);
...
// 步驟六:繪制View的裝飾(例如滾動條等等)
onDrawForeground(canvas)
}
-
setWillNotDraw的作用
// 如果一個View不需要繪制任何內(nèi)容,那么設(shè)置這個標(biāo)記位為true以后还绘, // 系統(tǒng)會進(jìn)行相應(yīng)的優(yōu)化楚昭。 public void setWillNotDraw(boolean willNotDraw) { setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); }
認(rèn)情況下,View沒有啟用這個優(yōu)化標(biāo)記位拍顷,但是ViewGroup會默認(rèn)啟用這個優(yōu)化標(biāo)記位抚太。
件繼承于ViewGroup并且本身不具備繪制功能時,就可以開啟這個標(biāo)記位從而便于系統(tǒng)進(jìn)行后續(xù)的優(yōu)化昔案。
個ViewGroup需要通過onDraw來繪制內(nèi)容時尿贫,我們需要顯示地關(guān)閉WILL_NOT_DRAW這個標(biāo)記位
[原文參考鏈接]https://jsonchao.github.io/2018/10/28/Android%20View%E7%9A%84%E7%BB%98%E5%88%B6%E6%B5%81%E7%A8%8B/)