View的工作原理

View工作原理

PhoneWindow

View工作原理-PhoneWindow

Window是一个抽象类,提供了各种窗口操作方法。每个Activity都会持有一个Window

PhoneWindowWindow唯一实现类,在Activity被创建时Activity.attach()进行初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//ActivityThread.java
//Activity开始启动
public Activity handleLaunchActivity(ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent)
{
...
final Activity a = performLaunchActivity(r, customIntent);
}

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//创建Application对象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
//Activity初始化
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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//Activity.java
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, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback)
{
...
//初始化PhoneWindow对象
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
//绑定Window对象
mWindow.setCallback(this);

}
1
2
3
4
5
//PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;//对应DecorView
}

DecorView

View工作原理-DecorView

DecorView是整个Window界面的最顶层View。 可以使用Android Studio自带的Layout Inspector查看页面层级

DecorView的布局结构

一般情况下DecorView会包含一个竖直方向的LinearLayout,该LinearLayout分为上下两个部分,上面是标题栏(titlebar),下面是内容栏(继承自FrameLayout 且id为content)。因此我们设置Activity的布局方法叫做setContentView(),因为他们都被加进了id为content的FrameLayout中。

我们可以利用ViewGroup content = findViewById(R.android.id.content)获取conetnt。使用content.getChildAt(0)获取设置的Activity布局。

1
2
3
4
5
6
7
8
9
10
11
// ../android/app/Activity.java
public <T extends View> T findViewById(@IdRes int id) {
//从Window中去获取View
return getWindow().findViewById(id);
}

// ../android/view/Window.java
public <T extends View> T findViewById(@IdRes int id) {
//从DecorView获取View
return getDecorView().findViewById(id);
}

所有的View都会从DecorView中开始检索,所以View层的事件都会先经过DecorView,再传递到我们定义的View上

setContentView()过程

通过setContentView()将需要加载的布局放到DecorView

1
2
3
4
5
//Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}

Activity.setContentView()调用PhoneWindow.setContentView()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
//创建DecorView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}

if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//开始加载对应布局
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}

setContentView()主要执行以下两步:

installDecor()——创建DecorView

基础DecorView主要包含两部分,标题title_bar和内容content

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//PhoneWindow.java
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//生成DecoeView
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//根据DecorView生成子View
mContentParent = generateLayout(mDecor);
...
}
}

protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
// 生成DecorView对象
return new DecorView(context, featureId, this, getAttributes());
}

protected ViewGroup generateLayout(DecorView decor) {
...
int layoutResource;
int features = getLocalFeatures();
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
}...
else{
layoutResource = R.layout.screen_simple; //默认布局
}
mDecor.startChanging();
//开始加载布局
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//根据id找到 content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);//com.android.internal.R.id.content
...
return contentParent;
}

//DecorView.java
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
//解析得到的View放到DecorView中
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
}

installDecor()主要负责创建DecorView并执行generateLayout()生成contentParent将自定义的布局放入其中。

inflate(layoutResID, mContentParent)——加载布局

inflate()主要将layoutResID加载成具体的View,并加入到mContentParent中,进行显示。

执行流程

ViewRootImpl

View工作原理-ViewRootImpl

ViewRoot对应于ViewRootImpl类,是连接WindowManager和DecorView的纽带,View的三大流程均需通过ViewRoot完成。

ViewRootImpl创建时机

当Activity创建时,最终是调用到ActivityThreadhandleLaunchActivity来创建Activity。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ../android/app/ActivityThread.java
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
//创建一个Activity 会调用到onCreate()方法 从而完成DecroView的创建
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;

handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
...
}
...
}

上述方法后续调用到了handleResumeActivity(),在这个方法中调用到了WindowManager.addView()将View传递至WindowManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// ../android/app/ActivityThread.java
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason)
{
ActivityClientRecord r = mActivities.get(token);
if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
return;
}
unscheduleGcIdler();
mSomeActivitiesChanged = true;

// 在这里会调用到生命周期中的onResume方法
r = performResumeActivity(token, clearHide, reason);
...
if(r!=null){
...
final Activity a = r.activity;
...
//获得当前Activty的Window对象
r.window = r.activity.getWindow();
//获得当前Window的DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//获得当前Activity的WindowManager对象
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//将DecorView添加到PhoneWindow中
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}

// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(
TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
}
if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
...
if (r.activity.mVisibleFromClient) {
//显示DecorView
r.activity.makeVisible();
}
}
...
}

//Activity.java
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
//显示DecorView及其内容
mDecor.setVisibility(View.VISIBLE);
}

后续调用到了wm.addView()。将对应的DecorView传递进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// ../android/view/WindowManagerImpl.java
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Window mParentWindow;
...
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
//调用到了WindowManagerGlobal中的addView
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
...
}

// ../android/view/WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow)
{
...

ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
//创建了ViewRootImpl实例
root = new ViewRootImpl(view.getContext(), display);//初始化了ViewRootImpl对象
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
//调用setView 将传进来的DecorView添加到PhoneWindow中。
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}

经过ActivityThread.handleResumeActivity() -> WindowManagerGlobal.addView()创建了ViewRootImpl对象

与DecorView的关系

上述流程走完后,就把DecorView加载到了Window中。这个流程中将ViewRootImpl对象与DecorView进行了关联

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//view 表示 DecorView 
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized(this){
//传进来的DecorView作为全局变量使用
mView = view;
...
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
//绘制整个布局
requestLayout();
...
//设置ViewRootImpl为DecorView的parentView
view.assignParent(this);
}
}

执行到ViewRootImpl.setView()设置DecorView,assignParent(root)。表示ViewRootImpl是DecorView的parent

三者关系

ActivityWindow(PhoneWindow)View(DecorView)ViewRootImpl之间的关系?

PhoneWindowWindow的唯一子类,在Activity.attach()构建的实例,是Activity与View交互的中间层

DecorView所有View的最顶层,ViewRootImplDecorViewparent,负责WindowManagerServiceDecorView的通信。掌管View的各种事件,例如刷新、点击事件等

LayoutInflater

LayoutInflater是一个抽象类,具体实现类为PhoneLayoutInflater。主要用于进行布局加载

PhoneLayoutInflater

View工作原理-LayoutInflater-PhoneLayoutInflater

通过系统注册服务可以得到LayoutInflater的实现类PhoneLayoutInflater

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//LayoutInflater.java
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);//获取系统配置的加载服务
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}

//ContextThemeWrapper.java
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
//单例获取 LayoutInflater
if (mInflater == null) {
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}

//SystemServerRegistry.java
//系统设置 PhoneInflater 为加载类
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>()
{
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});

PhoneLayoutInflater设置的contextContextImpl.getOuterContext()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//ContextImpl.java
final void setOuterContext(Context context) {
mOuterContext = context;
}

final Context getOuterContext() {
return mOuterContext;
}

//ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
appContext.setOuterContext(activity);//设置外部context为Activity
}

一般从Activity、View、Dialog,Fragment获取的layoutInflater.getContext()为Activity

无论是哪种方式获取LayoutInflater,都是通过ContextImpl.getSystemService()获取的。

inflate()-加载布局

LayoutInflater-inflate

source:需要加载的layout id

root:根布局(为null则表示创建的是最顶层的布局)

*attachToRoot:是否添加到root

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
...
//构造xml解析器
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;

try {
// Look for the root node.
int type;
//非xml起始与结尾标记
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
}

if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}

final String name = parser.getName();
//处理<merge>标签
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//传入rootview 解析得到的布局直接加入rootView中
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
//根据Tag创建对应的View 例如<TextView>
final View temp = createViewFromTag(root, name, inflaterContext, attrs);

ViewGroup.LayoutParams params = null;

if (root != null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}

// Inflate all children under temp against its context.
//创建temp子View
rInflateChildren(parser, temp, attrs, true);

// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
//将temp添加到rootView中
root.addView(temp, params);
}

// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
//attachToRoot:将View添加到RootView中,非就是直接返回解析的子View
result = temp;
}
}

} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;

Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}

return result;
}
}

void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate)
throws XmlPullParserException, IOException
{

final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;

while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

if (type != XmlPullParser.START_TAG) {
continue;
}

final String name = parser.getName();

if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {//<include>
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {//<merge>
throw new InflateException("<merge /> must be the root element");
} else {
//创建View
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//递归创建子View
rInflateChildren(parser, view, attrs, true);
//创建的子View添加会parent
viewGroup.addView(view, params);
}
}

if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}

if (finishInflate) {
parent.onFinishInflate();
}
}

layoutInflater.inflate()主要是调用createViewFromTag()从xml生成view的。

* createViewFromTag()

主要负责将<tag>创建成View对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
//LayoutInflater.java
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr)
{
/**
* view标签 取class 做为name
*/

if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}

// 设置View的Theme
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
//处理 <blink>标签
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}

try {
View view;
//通过Factory /Factory2 进行View的实例化
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
//通过 mPrivateFactory实例化View
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
//未设置 Factory,走默认创建View的流程
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//<tag>中存在 . 可以判断为自定义View,走View自身的创建流程
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}

return view;
}
,,,
}
}

createViewFromTag()主要做了以下几步:

  1. 如果为<view>标签,读取class属性做为类名

    1
    <view class="LinearLayout"/> 等价于<LinearLayout></LinearLayout>
  1. 应用ContenxtThemeWrapper为View设置主题Theme

  2. 使用Factory/Factory2/mPrivateFactory实例化View,相当于拦截

    实例化View的优先顺序为Factory2 > Factory > mPrivateFactory > PhoneLayoutInflater

  3. 未设置以上factory,执行View的默认创建流程

    主要通过PhoneLayoutInflater执行

Factory/Factory2-拦截View创建

LayoutInflater-Factory2

在上节有说到Factory/Factory2执行相当于拦截的功能,hookView创建的流程

mPrivateFactory实现了Factory2接口,主要用于拦截<fragment>标签处理

1
2
3
4
5
6
7
8
9
10
11
private Factory mFactory;
private Factory2 mFactory2;
private Factory2 mPrivateFactory;

public interface Factory {
public View onCreateView(String name, Context context, AttributeSet attrs);
}

public interface Factory2 extends Factory {
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}

Factory2相对于FactoryonCreateView()多传入了parent

Factroy2

设置Factroy2的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void setFactory2(Factory2 factory) {
if (mFactorySet) { //只允许设置一次 Factory2
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {//设置的factory不能为null
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
//控制factory调用顺序
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private static class FactoryMerger implements Factory2 {
private final Factory mF1, mF2;
private final Factory2 mF12, mF22;

FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
mF1 = f1;
mF2 = f2;
mF12 = f12;
mF22 = f22;
}

//此处对应Factory
public View onCreateView(String name, Context context, AttributeSet attrs) {
View v = mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF2.onCreateView(name, context, attrs);
}
//此处对应Factory2
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
: mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
: mF2.onCreateView(name, context, attrs);
}
}

最终都是通过FactoryMerger执行的onCreateView

何处调用setFactory2()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//AppCompatActivity.onCreate() -> AppCompatDelegate.installViewFactory() ->
//AppCompatDelegateImpl.installViewFactory()
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
}
}

//设置Factory2执行到 onCreateView()
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return this.createView(parent, name, context, attrs);
}

public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
if (this.mAppCompatViewInflater == null) {
TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
String viewInflaterClassName = a.getString(styleable.AppCompatTheme_viewInflaterClass);
if (viewInflaterClassName != null && !AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
try {
Class viewInflaterClass = Class.forName(viewInflaterClassName);
this.mAppCompatViewInflater = (AppCompatViewInflater)viewInflaterClass.getDeclaredConstructor().newInstance();
} catch (Throwable var8) {
Log.i("AppCompatDelegate", "Failed to instantiate custom view inflater " + viewInflaterClassName + ". Falling back to default.", var8);
this.mAppCompatViewInflater = new AppCompatViewInflater();
}
} else {
this.mAppCompatViewInflater = new AppCompatViewInflater();
}
}

...
return this.mAppCompatViewInflater.createView(...);
}

通过AppCompatViewInflater去执行View的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//AppCompatViewInflater.java
final View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
...
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
...
}
}

@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}

此处可以在使用到AppCompatActivity时,将原先的<TextView>转换为AppCompatTextView

mPrivateFactory

系统hide对象,无法被外界使用,主要处理<fragment>

1
2
3
4
5
6
7
public void setPrivateFactory(Factory2 factory) {
if (mPrivateFactory == null) {
mPrivateFactory = factory;
} else {
mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
}
}
何处调用setPrivateFactory()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Activity.java
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, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback)
{
...
mWindow.getLayoutInflater().setPrivateFactory(this); //this 表示当前Activity
...
}

public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if (!"fragment".equals(name)) {
return onCreateView(name, context, attrs);
}
//<fragment>标签直接解析执行 onCreateView
return mFragments.onCreateView(parent, name, context, attrs);
}

拓展使用

系统通过Factory提供hook方法,方便拦截LayoutInflater创建View的过程。支持以下应用场景:

  • 支持对自定义标签名称的处理
  • 全局替换系统控件为自定义View
  • 替换字体
  • 全局换肤
  • 获取控件加载耗时

针对以上场景,实现部分关键代码以供参考

获取控件加载耗时
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//XXActivity.java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//设置自定义Factory
setFactory2();
super.onCreate(savedInstanceState);
setContentView(R.layout.act_xx);
}

private void setFactory2(){
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
long startTime = System.currentTimeMillis();//加载开始时间
View view = getDelegate().createView(parent, name, context, attrs);//开始加载
long costTime = System.currentTimeMillis() - startTime;//加载结束时间
Log.e("costTime",costTime+"");
return view;
}

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
}

setFactory2()不可以放到super.onCreate()之后,会触发A factory has already been set on this LayoutInflater异常,原因就在于setFactory2()最多支持设置一个。

替换字体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private void setFontFactory2() {
LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View view = getDelegate().createView(parent, name, context, attrs);
if (view instanceof TextView ) {
((TextView) view).setTypeface(XX);//设置对应字体
}
return view;
}

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
}

只要执行了getDelegate().createView()可以保证原生控件->兼容控件功能正常。

View默认创建流程

LayoutInflater-默认View创建流程

未设置Factory/Factory2就会执行默认的View创建流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//LayoutInflater.java
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr)
{
...
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);//系统提供View
} else {
view = createView(name, null, attrs);//自定义View
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
...
}

系统提供View

例如<TextView/>、<Button/>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class PhoneLayoutInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};

@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}

return super.onCreateView(name, attrs);
}

}

//LayoutInflater.java
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException
{
return createView(name, "android.view.", attrs);
}

优先判断

  • android.widget.* 例如android.widget.TextView
  • android.webkit.* 例如android.webkit.WebView
  • android.app.* 例如android.app.ActionBar

是否有对应nmae的View实例存在

都不存在,就在android.view.*找寻对应View实例 例如android.view.ViewStub

自定义View

例如android.support.v7.widget.RecyclerView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException
{
//构建缓存,缓存已加载的View
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;

try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
//新建View构造器
if (constructor == null) {
// 得到全限定名 例如android,widget.TextView
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);

if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);

boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}

Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
//根据得到的 constuctor 实例化View对象
final View view = constructor.newInstance(args);
//针对ViewStub特殊处理
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;

}
...
}

执行流程

View默认创建流程分为:

  • <tag>不包含.,用于处理<TextView>、<WebView>等标签,此时需要拼接android.widget. 或 android.webkit. 或 android.app.前缀(实现位于PhoneLayoutInflater),都没有找到对应的View实例时,就会在添加android.view.再去加载。
  • <tag>包含.,此时的实例View分为以下几步:
    • 构建View的缓存,缓存的是constructor,根据name获取constructor
    • 缓存中不存在时,需要根据prefix+name获取View的constructor,并存入缓存中
    • 根据constructor构造View实例——constructor.newInstance()
    • 如果需要处理ViewStub,为ViewStub指定加载类

总结

LayoutInflater过程

View的绘制流程触发

调用了ViewRootImpl.setView(decorView)将DecorView与ViewRootImpl进行了关联。View的绘制流程就是从ViewRoot开始的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized(this){
//传进来的DecorView作为全局变量使用
mView = view;
...
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
//绘制整个布局
requestLayout();
...
//设置ViewRootImpl为DecorView的parentView
view.assignParent(this);
}
}

//请求刷新整个布局
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}

void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//添加同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}

final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//移除同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
//这里开始View的绘制流程
performTraversals();

if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}

ViewRootImpl.setView()中最后调用到了performTraversals()在这个方法中开始View的绘制流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
private void performTraversals() {
...
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);

...
if (!mStopped || mReportNextDraw) {
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
2 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
if(layoutRequested){
//开始Measure过程,定义View的宽高
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
}
}

final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if(didLayout){
//开始Layout过程,决定View的位置
performLayout(lp, mWidth, mHeight);
...
}

if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
//开始Draw过程,决定了View的显示,这个过程结束才可以看到内容
performDraw();
}
}

通过以上流程分析:View的绘制流程是从ViewRootImpl中开始的,先调用performTraversals()开始绘制,随后调用内部的performMeasure()开始Measure过程,调用performLayout(),开始Layout过程,最后调用performDraw()开始Draw,完成后就可以现在在屏幕上。

View绘制流程

如上图所示,performTraversals()依次调用performMeasure(),performLayout(),performDraw()完成View的绘制。

View工作流程

主要是指measure(测量),layout(布局),draw(绘制)三大流程。

measure-测量

起点位于performMeasure()

1
2
3
4
5
6
7
8
9
//ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

MeasureSpec

measure-MeasureSpec

MeasureSpec代表一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(某种测量模式下的规格大小)。

作用:父控件提供给子View的一个参数,作为设定自身大小参考,实际大小还是有子View自身决定。

MeasureSpec结构

结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;

/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}

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;

@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}

public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}

SpecMode分为三类:

  • UNSPECIFIED未指定模式。父控件不对子控件添加束缚,子元素可以为任意大小,一般用于系统内部的测量。比如ScrollView
  • EXACTLY精确模式。父控件为子View指定精确大小,希望子View完全按照自己给的尺寸处理大小。一般是设置了明确的值或是MATCH_PARENT
  • AT_MOST最大模式。父控件为子View指定最大尺寸,希望子View不要超过这个尺寸。一般对应WRAP_CONTENT
MeasureSpec与LayoutParams的关系

每一个View,都持有一个MeasureSpec,里面保存了View的尺寸。我们也可以使用LayoutParams指定View的尺寸。所以在View测量的时候,系统会将LayoutParams在父容器的约束下转换成MeasureSpec,然后根据转换后的值确定宽高。

转换后的MeasureSpec是由LayoutParams和父容器的MeasureSpec一起决定的。

下:childLayoutParams 右:parentSpecMode EXACTLY AT_MOST UNSPECIFIED
固定大小 Exactly
childSize
Exactly
childSize
Exactly
childSize
match_parent Exactly
parentSize(父容器剩余空间)
AT_MOST
parentSize(最大父容器剩余空间)
UNSPECIFIED
0 或 parentSize(最大父容器剩余空间)
wrap_content AT_MOST
parentSize(最大父容器剩余空间)
AT_MOST
parentSize(最大父容器剩余空间)
UNSPECIFIED
0 或 parentSize(最大父容器剩余空间)

根据ViewGroup.getChildMeasureSpec()得出上表。

DecorView转换MeasureSpec

DecorView的转换由Window的尺寸和自身的LayoutParams决定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ../android/view/ViewRootImpl.java
private void performTraversals() {
...
//DecorView Measure过程
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec,childHeightMeasureSpec)
...
}

//在方法中生成了DecoeView的MeasureSpec 根据Window的尺寸和自身的LayoutParams
private static int getRootMeasureSpec(int windowSize/*Window尺寸*/, int rootDimension) {
int measureSpec;
switch (rootDimension) {

case ViewGroup.LayoutParams.MATCH_PARENT:
//MeasureSpec中的specSize就是窗口尺寸,specMode为EXACTLY 精确模式
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
//MeasureSpec中的specSize为窗口尺寸,specMode为aT_MOST 最大模式,最大值为窗口尺寸
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
//MeasureSpec中的specSize为固定尺寸,specMode为EXACTLY 精确模式
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}

View的measure过程

measure-View的measure过程

主要是由measure()方法完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// ../android/view/View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
//需要执行onMeasure
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// 布局发生变化
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
...
if (forceLayout/*强制测量*/ || needsLayout/*需要测量*/) {
...
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
//需要强制测量布局 或者。缓存无效
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
//需要在layout 再次执行 onMeasure
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
...
//添加 PFLAG_LAYOUT_REQUIRED标记,表示需要执行 layout流程
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
}

measure()中调用onMeasure()去进行实际的测量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//../android/view/View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

//设置View的宽高
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;

measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

//返回View的MeasureSpec中的specSize
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://wrap_content
case MeasureSpec.EXACTLY://match_parent / XX
//这段代码中可以分析得出 一个直接继承View的自定义View 定义为wrap_content和match_parent大小都是一致的.
result = specSize;
break;
}
return result;
}

protected int getSuggestedMinimumHeight() {
return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

//如果View没有设置背景,返回minWidth值,默认为0。若设置了背景就取背景宽度和最小宽度中的最大值返回。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}


// ../android/graphics/drawable/Drawable.java
public int getMinimumWidth(){
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

View-Measure

结合上述流程图,简单分析View的Measure过程

  • 系统在绘制开始时回去调用View.measure(),这个类是final的们无法被重写
  • 后续调用View.onMeasure(),自定义View时可以按照自己的需求对这个方法进行重写
  • onMeasure()中调用到setMeasureDimension()对View进行宽高的设置
  • 需要使用getDefaultSize()去获取最终显示出的宽高
  • getDefaultSize()中需要对传进来的MeasureSpec进行分析处理
    • SpecMode若为UNSPECIFIED,则最终尺寸为传进来的SpecSize
    • SpecMode为AT_MOST,EXACTLY,还需要额外判断View是否有背景
      • 有背景,最终尺寸就为View的最小尺寸和背景尺寸的最大值
      • 没背景,最终尺寸就为View的最小尺寸
  • 取到最终尺寸后,数据回溯到onMeasure()中,即完成测量(Measure)过程

在上述分析中,自定义View中使用wrap_content时,specMode为AT_MOST,尺寸为父控件剩余大小,效果与使用match_parent一致。这也是自定义View中常碰到的问题 为何自定义View是wrap_content无效? 解决方法就是 自己重写onMeasure()wrap_content特殊处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void onMeasure(int widthMeasureSpec,int heightMeasureSpec){
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

if(widthSpecMode = MeasureSpec.AT_MOST && heightSpecMode = MeasureSpec.AT_MOST){
setMeasureDimension(mWidth,mHeight);
}else if(widthSpecMode = MeasureSpec.AT_MOST){
setMeasureDimension(mWidth,heightSpecSize);
}else if(heightSpecMode = MeasureSpec.AT_MOST){
setMeasureDimension(widthSpecSize,mHeight);
}

}

ViewGroup的measure过程

measure-ViewGroup的measure过程

除了完成自身的measure过程之外,还要去遍历调用所有子元素的measure方法,各个子元素再去递归执行这个过程。

先Measure子View,再Measure自己

ViewGroup中没有定义onMeasure(),定义了一个measureChildren()

1
2
3
4
5
6
7
8
9
10
11
12
// ../android/view/ViewGroup.java
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];
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
//遍历对每一个子元素进行测量过程
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}

循环调用measureChild()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// ../android/view/ViewGroup.java
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec)
{
//获得子View的LayoutParams
final LayoutParams lp = child.getLayoutParams();
//
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

//子View的MEasureSpec由父View的MEasureSpec以及自身的LayoutParams共同决定
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);

//padding代指父View已占用的空间,子View无法使用,所以子View的空间需要减去padding部分
int size = Math.max(0, specSize - padding);

int resultSize = 0;
int resultMode = 0;

switch (specMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;

// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
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);
}

由于ViewGroup有不同布局的需要,很难统一,所以没有提供统一的onMeasure()方法,而是让子类自己去实现onMeasure()

ViewGroup-Measure

根据上述流程图,简单总结一下:

  • ViewGroup调用自身的measureChildren(),里面遍历自己的子View
  • 遍历后调用measureChild(),准备给每一个子View计算它的MeasureSpec
  • 调用getChildMeasureSpec()计算子View的MeasureSpec,需要结合父布局的MeasureSpec以及子View的LayoutParams共同得出结果
  • 调用子View的measure(),完成子View的测量过程。
  • 合并子View的测量值,得到ViewGroup的测量值

拓展

  1. 在Activity启动时获取View的尺寸?

    • 在 Activity#onWindowFocusChanged 回调中获取宽高。
      当Activity得到焦点或失去焦点的时候,这个方法都会被频繁调用
    • view.post(runnable),在 runnable 中获取宽高。
      利用Handler通信机制,发送一个Runnable在MessageQuene中,当layout处理结束时则会发送一个消息通知UI线程,可以获取到实际宽高。
    • ViewTreeObserver 添加 OnGlobalLayoutListener,在 onGlobalLayout 回调中获取宽高。
      监听全局View的变化事件,使用后需要注意移除OnGlobalLayoutListener 监听,以免造成内存泄露
    • 调用 view.measure(),再通过 getMeasuredWidth 和 getMeasuredHeight 获取宽高
      手动对view进行measure来得到View的尺寸。
  2. 不同ViewGroup实现的getChildMeasureSpec()可能导致不同结果

    每种ViewGroup的子类测量策略(getChildMeasureSpec())不尽相同,就会导致显示结果不一致。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <FrameLayout
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:background="@color/colorAccent">


    <Button
    android:layout_width="match_parent"
    android:layout_height="400dp" />

    </FrameLayout>

    <RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="300dp">


    <Button
    android:layout_width="match_parent"
    android:layout_height="400dp" />

    </RelativeLayout>

FrameLayoutRelativeLayout中,Button显示效果不一致,

  • FrameLayoutButton高度为400dp
  • RelativeLayoutButton高度为300dp

    FrameLayout遵循默认的ViewGroup.getChildMeasureSpec()RelativeLayout重写getChildMeasureSpec()

layout-布局

ViewGroup用来确定子元素的位置,当ViewGroup位置被确定后,在onLayout()中遍历所有子View,并调用其layout()

先layout自身后layout子元素。

起点位于performLayout()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight)
{
//正在执行layout流程
mInLayout = true;

final View host = mView;//对应DecorView
//执行DecorView的layout过程
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//在layout过程中 尚未执行requestLayout的view
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
//寻找mPrivateFlags为PFLAG_FORCE_LAYOUT的View
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
//当前View重新measure
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
//重新执行layout
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
//寻找尚未设置 PFLAG_FORCE_LAYOUT的View
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// 执行requestLayout在下一帧执行时
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}
}

}

主要执行了三步:

  • 执行DecorViewlayout过程
  • 执行调用过requestLayout()的View(包含PFLAG_FORCE_LAYOUT标志)的measurelayout
  • 还没调用过requestLayout()的View加入到队列中,等待下一帧绘制时执行

View的layout过程

layout-View的layout过程

主要是由View的layout()方法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// ../android/view/View.java   
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
//需要再次测量 可能缓存无效
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}

//左上角顶点距父容器左边的距离
int oldL = mLeft;
//左上角顶点距父容器上边的距离
int oldT = mTop;
//右下角顶点距父容器上边的距离
int oldB = mBottom;
//右下角顶点距父容器上边的距离
int oldR = mRight;
//判断本次布局流程是否发生了布局的变化
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//通知View进行布局过程
onLayout(changed, l, t, r, b);
...
//通知View布局发生变化
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
...
}

//由于子View下是没有子类了,所以该方法内不没有任何代码实现 一般自定义View是不需要重写该方法的
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

}

private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
//根据特效边框重新计算四个顶点的位置,然后调用setFrame重新计算
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}

//保存本次布局信息
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;

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;
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

// 布局发生变化 需要执行重绘流程
invalidate(sizeChanged);
//重新计算View的四个顶点距父布局左上边框的距离
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
...
}
}


//判断当前View是否存在阴影或者外发光等边框效果
public static boolean isLayoutModeOptical(Object o) {
return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}

View-Layout

按照流程图总结一下:

  • View调用layout()开始布局过程(确定最终宽高以及四个顶点的位置)
  • 根据是否有边缘效果(例如发光,阴影)
    • 有边缘效果,调用setOpticalFrame()去除边缘的影响,最终还是调用setFrame()设立自己的四个顶点
    • 无边缘效果,调用setFrame()设立自己的四个顶点
  • 最后调用onLayout()最终确立宽高以及四点坐标。

ViewGroup的layout过程

layout-ViewGroup的layout过程

当有子View存在的时候,需要遍历子View进行layout过程。即需要在onLayout()方法实现子View的layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//ViewGroup.java
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
//调用View.layout()
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}

@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b)
;

//源码与上述相同 由于ViewGroup中所有子View的layout都需要实现,所以需要实现 onLayout() 方法
protected void onLayout(boolean changed,int left,int top,int right,int bottom){
//遍历子View
for (int i =0 ; i <getChildCount();i++){
View child = getChildAt(i);

//在这里可以添加 顶点变化逻辑
int childTop = Top;
int childLeft = Left;
int childBottom = Bottom;
int childRight = Right;

...
setChildFrame(child,childLeft,childTop,childRight,childBottom);
}
}

private void setChildFrame(child,int l,int t,int r,int b){
//按照上一节流程走
child.layout(l,t,r,b);
}

ViewGroup-Layout

按照流程图简单总结一下:

  • 先调用ViewGroup的layout(),先对ViewGroup进行布局过程
  • 在ViewGroup的onLayout()中实现子View的遍历布局过程
  • 对遍历的子View按照ViewGroup的要求进行顶点坐标的计算,计算完成后调用子View的layout()

拓展:

  1. View的测量宽/高(getMeasureWidth()/getMeasureHeight())与最终得到的宽/高(getWidth()/getHeight())有什么区别?

    layout-getMeasureWidth() 与 getWidth()区别 与 getWidth()区别.png)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //	获得View在测量过程中的宽
    public final int getMeasuredWidth() {
    return mMeasuredWidth & MEASURED_SIZE_MASK;
    }
    // 获得View在测量过程中的高
    public final int getMeasuredHeight() {
    return mMeasuredHeight & MEASURED_SIZE_MASK;
    }
    // 上节 measure 源码分析中就是调用了该方法 进行View的测量
    private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;
    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
    }
    //获得View最终宽
    public final int getWidth() {
    return mRight - mLeft;
    }
    //获得View最终高
    public final int getHeight() {
    return mBottom - mTop;
    }

两者的比较

类型 何时赋值 赋值方法 使用场景
View测量结束宽/高
getMeasureWidth()/getMeasureHeight()
View的measure过程 setMeasureDimension() onLayout()获取View的宽/高
View最终宽/高
getWidth()/getHeight()
View的layout过程 layout()对top,left,right,bottom进行操作 onLayout()结束后获取最终宽/高

一般情况下,二者返回的数据是相同的,除非人为对View的layout()进行重写。

1
2
3
public void layout(int l,int t,int r,int b){
super.layout(j,t,r+100,b+100);
}

上述代码就会导致View最终结果与测量时不同。

draw-绘制

draw作用主要将View绘制在屏幕上面

draw过程,先draw自身再draw子View

起点是performDraw()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//ViewRootImpl.java
private void performDraw() {
...
boolean canUseAsync = draw(fullRedrawNeeded);
}

private boolean draw(boolean fullRedrawNeeded) {
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
//硬件绘制
...
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
} else {
//软件绘制
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
...
}

View的draw过程

draw-View的draw过程

View的draw过程,从View.draw()开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// ../android/view/View.java
public void draw(Canvas canvas) {
//标记当前View是否背景透明
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

int saveCount;
//1. 绘制背景
if (!dirtyOpaque) {
drawBackground(canvas);
}
final int viewFlags = mViewFlags;
//是否有水平边缘
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
//是否有竖直边缘
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if(!horizontalEdges && !verticalEdges){
// 3.绘制View本身
if (!dirtyOpaque) onDraw(canvas);

// 4.绘制子View
dispatchDraw(canvas);

// 6.绘制装饰 例如滚动条
onDrawForeground(canvas);

...
return;
}

//如果有竖直边缘或者水平边缘 例如divide

// 2. 保存当前Canvas层
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);
}
...
// 3.绘制View本身
if (!dirtyOpaque) onDraw(canvas);

// 4.绘制子View
dispatchDraw(canvas);

...
// 5.绘制边缘效果 例如阴影
canvas.restoreToCount(saveCount);
...

// 6.绘制装饰 例如滚动条
onDrawForeground(canvas);
...

}

//绘制View本身的背景
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
//设置View的背景边界
setBackgroundBounds();
...

final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
//将画布偏移 然后在偏移后的画布上进行背景绘制
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}

//绘制View本身的内容
protected void onDraw(Canvas canvas) {
// 默认空实现 需要子类复写该方法以实现内容的绘制 ,自定义View中必须执行该方法
}

//绘制子View的内容
protected void dispatchDraw(Canvas canvas) {
//由于View不存在子View,所以不需要实现
}

//绘制装饰 例如滚动条 前景图片
public void onDrawForeground(Canvas canvas) {
onDrawScrollIndicators(canvas);
onDrawScrollBars(canvas);

final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
if (foreground != null) {
if (mForegroundInfo.mBoundsChanged) {
mForegroundInfo.mBoundsChanged = false;
final Rect selfBounds = mForegroundInfo.mSelfBounds;
final Rect overlayBounds = mForegroundInfo.mOverlayBounds;

if (mForegroundInfo.mInsidePadding) {
selfBounds.set(0, 0, getWidth(), getHeight());
} else {
selfBounds.set(getPaddingLeft(), getPaddingTop(),
getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
}

final int ld = getLayoutDirection();
Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
foreground.setBounds(overlayBounds);
}

foreground.draw(canvas);
}
}

View-Draw

结合上述流程图分析Draw过程:

  • 先调用View.draw()方法开始Draw流程
  • 如果需要dirtyOpaque,就绘制背景drawBackground()
  • 如果需要显示边缘效果,就进行保存画布canvas.saveLayer()
  • 如果需要dirtyOpaque,绘制自身的内容onDraw()自定义View必须实现
  • 调用dispatchDraw()绘制子View
  • 如果需要显示边缘效果,绘制后,还原画布canvas.restore()
  • 调用drawForeground()绘制装饰,例如滚动条或前景

ViewGroup的draw过程

draw-ViewGroup的draw过程

ViewGroup的draw过程主要调整了上述源码中的dispatchDraw(),在其内部进行了子View的遍历以及绘制过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 // ../android/view/ViewGroup.java
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
final int childrenCount = mChildrenCount;
final View[] children = mChildren;
...
for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
transientChild.getAnimation() != null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1;
}
}

final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
...
}

//绘制子View
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
//调用子View的draw方法
return child.draw(canvas, this, drawingTime);
}

ViewGroup-Draw

结合上述流程图分析ViewGroup的Draw过程:

  • draw过程与上述View的draw过程一致
  • dispatchDraw()默认实现,内部包含了子View的遍历以及绘制

拓展:

  1. View.setWillNotDraw()有什么意义?

    1
    2
    3
    4
    public void setWillNotDraw(boolean willNotDraw) {
    //设置 不需绘制 标记位
    setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }

    如果一个View不需要绘制任何内容,设置这个标记为true,系统就会进行相应优化。

    View默认不开启willNotDraw标记位,ViewGroup默认开启。

  2. ViewGroup修改子View绘制顺序

    draw-ViewGroup自定义绘制顺序

    ViewGroup.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    protected void dispatchDraw(Canvas canvas){
    ...
    //设置自定义绘制顺序
    final ArrayList<View> preorderedList = usingRenderNodeProperties
    ? null : buildOrderedChildList();
    //是否允许自定义绘制顺序
    final boolean customOrder = preorderedList == null
    && isChildrenDrawingOrderEnabled();
    for (int i = 0; i < childrenCount; i++) {
    while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
    final View transientChild = mTransientViews.get(transientIndex);
    if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
    transientChild.getAnimation() != null) {
    more |= drawChild(canvas, transientChild, drawingTime);
    }
    transientIndex++;
    if (transientIndex >= transientCount) {
    transientIndex = -1;
    }
    }

    final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
    final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
    more |= drawChild(canvas, child, drawingTime);
    }
    }
    ...
    }

    protected boolean isChildrenDrawingOrderEnabled() {
    return (mGroupFlags & FLAG_USE_CHILD_DRAWING_ORDER) == FLAG_USE_CHILD_DRAWING_ORDER;
    }
    //设置是否允许自定义绘制顺序
    protected void setChildrenDrawingOrderEnabled(boolean enabled) {
    setBooleanFlag(FLAG_USE_CHILD_DRAWING_ORDER, enabled);
    }

    //初始子View的绘制顺序 按照z轴的值调整绘制顺序,z轴从大到小绘制
    ArrayList<View> buildOrderedChildList() {
    final int childrenCount = mChildrenCount;
    if (childrenCount <= 1 || !hasChildWithZ()) return null;

    if (mPreSortedChildren == null) {
    mPreSortedChildren = new ArrayList<>(childrenCount);
    } else {
    // callers should clear, so clear shouldn't be necessary, but for safety...
    mPreSortedChildren.clear();
    mPreSortedChildren.ensureCapacity(childrenCount);
    }

    final boolean customOrder = isChildrenDrawingOrderEnabled();
    for (int i = 0; i < childrenCount; i++) {
    // add next child (in child order) to end of list
    final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
    final View nextChild = mChildren[childIndex];
    final float currentZ = nextChild.getZ();

    // insert ahead of any Views with greater Z
    int insertIndex = i;
    while (insertIndex > 0 && mPreSortedChildren.get(insertIndex - 1).getZ() > currentZ) {
    insertIndex--;
    }
    mPreSortedChildren.add(insertIndex, nextChild);
    }
    return mPreSortedChildren;
    }

    //确定当前子View的绘制顺序
    private int getAndVerifyPreorderedIndex(int childrenCount, int i, boolean customOrder) {
    final int childIndex;
    if (customOrder) {
    final int childIndex1 = getChildDrawingOrder(childrenCount, i);
    if (childIndex1 >= childrenCount) {
    throw new IndexOutOfBoundsException("getChildDrawingOrder() "
    + "returned invalid index " + childIndex1
    + " (child count is " + childrenCount + ")");
    }
    childIndex = childIndex1;
    } else {
    childIndex = i;
    }
    return childIndex;
    }
    //需要重写该方法,调整绘制顺序
    protected int getChildDrawingOrder(int childCount, int i) {
    return i;
    }

    //调整当前子View顺序
    private static View getAndVerifyPreorderedView(ArrayList<View> preorderedList, View[] children,
    int childIndex)
    {
    final View child;
    if (preorderedList != null) {
    child = preorderedList.get(childIndex);
    if (child == null) {
    throw new RuntimeException("Invalid preorderedList contained null child at index "
    + childIndex);
    }
    } else {
    child = children[childIndex];
    }
    return child;
    }

根据上述源码,默认的绘制按照z轴从大到小的顺序进行绘制,如果需要修改绘制顺序的话,需要执行以下两步:

  1. setChildrenDrawingEnabled(true)打开自定义设置开关
  2. 继承ViewGroup后,重写getChildDrawingOrder()方法,设置对应的绘制顺序

    常用的RecyclerViewViewPager都实现了该方法,其中RecyclerView通过设置ChildDrawingOrderCallback也可以实现这个功能。

如果在addView()的场景下,可通过setElevation()setTranslationZ()setZ()去修改Z轴的坐标值。

自定义View

自定义View

自定义View需要了解View的层次、View的事件分发机制以及View的工作流程。

分类

1.继承View重写onDraw()

主要用于实现一些不规则的效果,不方便通过布局的组合方法可以直接实现,往往需要静态或者动态的显示一些不规则图形(圆形啥的)。

特殊形状的这种就需要重写onDraw()实现。一般需要额外支持wrtap_content,并且也需要处理padding方法。

2.继承ViewGroup派生特殊的Layout

主要用于实现自定义的布局,除了常用的一些布局外。实现的是几种View的组合形式

实现稍微复杂,需要合适的处理ViewGroup的onMeasure(),onLayout()以及子View的onMeasure(),onLayout()

3.继承特定的View(例如TextView)

这种比较常见,一般用于拓展已有View的功能。

实现比较简单,无需自己处理wrap_content以及padding

4.继承特定的ViewGroup(例如LinearLayout)

比较常见,当某种效果看起来很像几种View组合在一起的时候

实现比较简单,无需自己处理测量以及布局过程

注意事项

自定义View-注意事项

1.让View支持wrap_content

直接继承View或ViewGroup的控件,不重写onMeasure()并对AT_MOST进行处理,就无法达到需要的显示效果。

2.需要的话,让View支持padding

直接继承View的控件,需要在draw过程处理padding属性,不然padding属性无法起作用。

直接继承ViewGroup的控件,需要在onMeasure(),onLayout()处理自身的padding以及子View的marginmeasureChildWithMargin()

3.尽量不要在View中使用Handler

View内部提供了post方法,可以替代Handler使用

4.View中如果有线程或动画,需要及时停止

  1. 不处理有可能造成内存泄漏,View不可见时也需要停止线程和动画
  2. 包含View的Activity启动时,View的onAccachedToWindow()会调用
  3. 包含View的Activity退出或当前View被移除时,调用View.onDetachedFromWindow()时关闭线程和动画

5.View若有滑动冲突情况,需要处理

滑动冲突处理方法:

  • 外部拦截法:点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,不需要就放行

    重写父容器的onInterceptTouchEvent(),在方法内部拦截

  • 内部拦截法:父容器不拦截任何事件,所有事件交由子容器进行处理,如果子容器需要就消耗事件,不需要就返给父容器处理。

    重写父容器的onInterceptTouchEvent(),以及子容器的dispatchTouchEvent()。关键在于调用requestDisallowInterceptTouchEvent()控制父布局是否进行拦截。

一般推荐使用外部拦截法,符合事件分发流程。

实例

{% post_link 自定义View实践%} {% post_link 自定义ViewGroup实践%}

拓展

如何触发View的重新绘制?

通过调用invalidate()/postInvalidate()requestLayout()实现。

View工作原理-触发View的重新绘制

requestLayout

View工作原理-requestLayout

在需要刷新View的布局时会调用该函数。不应该在布局的过程中调用这个函数。

这个请求可能会在以下场景执行:当前布局结束、当前帧绘制完成,下次布局发生时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//View.java
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();

if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
//如果当前在layout过程中,且调用了 requestLayout,就需要直接返回
//等待下一次信号到来时执行
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
//设置强制刷新标记
mPrivateFlags |= PFLAG_FORCE_LAYOUT;//该标记可执行onMeasure()
mPrivateFlags |= PFLAG_INVALIDATED;//

if (mParent != null && !mParent.isLayoutRequested()) {
//向父布局继续请求刷新布局
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}

mParent对应父节点,一层层向上递归调用父节点requestLayout()。直到调用ViewRootImpl.requestLayout()结束.

1
2
3
4
5
6
7
8
9
10
11
//ViewRootImpl.java
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//检查当前是否主线程
checkThread();
//需要执行measure、layout
mLayoutRequested = true;
scheduleTraversals();
}
}

调用到scheduleTraversals()就是开始了View的绘制流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//ViewRootImpl.java 
private void performTraversals() {
//是否执行measure、layout过程
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
...
if (!mStopped || mReportNextDraw) {
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
updatedConfiguration) {
//执行测量流程
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
...
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) {
//执行布局流程
performLayout(lp, mWidth, mHeight);
}
...
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

if (!cancelDraw && !newSurface) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
//执行绘制流程
performDraw();
}
}

一开始调用的performMeasure()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}


//View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
//需要执行onMeasure
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
...
if (forceLayout || needsLayout) {
...
onMeasure(widthMeasureSpec, heightMeasureSpec);
...
//添加 PFLAG_LAYOUT_REQUIRED标记,表示需要执行 layout流程
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
}

继续向下调用performLayout()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight)
{
//正在执行layout流程
mInLayout = true;

final View host = mView;//对应DecorView
//在layout过程中 尚未执行requestLayout的view
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
//寻找mPrivateFlags为PFLAG_FORCE_LAYOUT的View
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
//当前View重新measure
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight);
//重新执行layout
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
//寻找尚未设置 PFLAG_FORCE_LAYOUT的View
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// 执行requestLayout在下一帧执行时
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}
}

}

主要执行了三步:

  • 执行DecorViewlayout过程
  • 执行调用过requestLayout()的View(包含PFLAG_FORCE_LAYOUT标志)的measurelayout
  • 还没调用过requestLayout()的View加入到队列中,等待下一帧绘制时执行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//View.java
public void layout(int l, int t, int r, int b) {
//判断当前位置是否发生变化
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//位置发生变化。或 需要layout
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//回调onLayout()
onLayout(changed, l, t, r, b);
...
//移除 PFLAG_LAYOUT_REQUIRED标志 在measure过程添加
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
...
}
...
//layout过程完成后 移除 PFLAG_FORCE_LAYOUT标志
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;

}

protected boolean setFrame(int left, int top, int right, int bottom) {
//位置发生变化时,就需要执行 invalidate()重绘View
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);
//需要重绘视图
invalidate(sizeChanged);
}
}

requestLayout()主要执行了以下几步:

  • 添加PFLAG_FORCE_LAYOUTPFLAG_INVALIDATED标记
  • measure执行需要判断PFLAG_FORCE_LAYOUT标记是否存在
  • measure执行后,添加PFLAG_LAYOUT_REQUIRED标记,可以去执行onLayout()
  • layout执行后,移除PFLAG_LAYOUT_REQUIREDPFLAG_FORCE_LAYOUT标记
  • layout过程中,如果位置发生了变化,会执行到invalidate(),可能会执行draw过程;如果未发生变化,就不会执行draw过程

invalidate/postInvalidate

View工作原理-Invalidate

invalidate()必须在主线程调用。postInvalidate()可以在子线程调用(通过handler发送消息到主线程调用)

主要用于请求View的重绘,只会影响到View的draw过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//View.java
public void postInvalidate() {
postInvalidateDelayed(0);
}

public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}

@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();//继续执行到invalidate()
break;
...
}
}

public void invalidate() {
invalidate(true);
}

最终执行到invalidateInternal()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//View.java
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate)
{
...
//当前View不可见
if (skipInvalidate()) {
return;
}

if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED//当前没有执行invalidate
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
//需要全量重绘
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
//添加重绘标记
mPrivateFlags &= ~PFLAG_DRAWN;
}
//添加当前View重绘标记
mPrivateFlags |= PFLAG_DIRTY;
//是否刷新缓存
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}

// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
//设置重绘区域 并把自身传递到父布局
p.invalidateChild(this, damage);
}
...
}
}

上述代码修改标记完成后,调用父类的invalidateChild()将需要重绘的区域(脏区域)传入。(ViewGroup以及ViewRootImpl都继承自ViewParent类)

脏区域:为了保证绘制的效率,控件树仅对需要绘制的区域进行重绘,需要重绘的区域成为脏区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
//ViewGroup.java
public final void invalidateChild(View child, final Rect dirty) {
//开启硬件加速
if (attachInfo != null && attachInfo.mHardwareAccelerated) {
// 更新DisplayList
onDescendantInvalidated(child, child);
return;
}
//当前View是否 不透明
final boolean isOpaque = child.isOpaque() && !drawAnimation &&
child.getAnimation() == null && childMatrix.isIdentity();
//全不透明 标记为 PFLAG_DIRTY_OPAQUE
//部分透明 标记为 PFLAG_DIRTY
int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;
...
do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
}

if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
}

// If the parent is dirty opaque or not dirty, mark it dirty with the opaque
// flag coming from the child that initiated the invalidate
if (view != null) {
if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
view.getSolidColor() == 0) {
opaqueFlag = PFLAG_DIRTY;
}
if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
}
}
//递归调用父布局的重绘方法
parent = parent.invalidateChildInParent(location, dirty);
//计算需要重绘区域
dirty.set((int) Math.floor(boundingRect.left),
(int) Math.floor(boundingRect.top),
(int) Math.ceil(boundingRect.right),
(int) Math.ceil(boundingRect.bottom));
...
} while (parent != null);

}

//ViewGroup.java
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
//将子View转换为当前View显示的位置
...
}
}

parent==null时,表示已经到了最顶层ViewRootImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//ViewRootImpl.java
public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
//检查当前是否主线程
checkThread();
if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);

if (dirty == null) {
invalidate();
return null;
} else if (dirty.isEmpty() && !mIsAnimating) {
return null;
}
...
//更新屏幕对应脏区域
invalidateRectOnScreen(dirty);

return null;
}

private void invalidateRectOnScreen(Rect dirty) {
final Rect localDirty = mDirty;
if (!localDirty.isEmpty() && !localDirty.contains(dirty)) {
mAttachInfo.mSetIgnoreDirtyState = true;
mAttachInfo.mIgnoreDirtyState = true;
}

// Add the new dirty rect to the current one
localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom);
// Intersect with the bounds of the window to skip
// updates that lie outside of the visible region
final float appScale = mAttachInfo.mApplicationScale;
final boolean intersected = localDirty.intersect(0, 0,
(int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
if (!intersected) {
localDirty.setEmpty();
}
if (!mWillDrawSoon && (intersected || mIsAnimating)) {
//执行绘制流程
scheduleTraversals();
}
}

通过scheduleTraversals()执行绘制流程,由于未设置mLayoutRequested = true,所以无法进入performMeasure()performLayout()只能执行到performDraw()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//ViewRootImpl.java
private void performDraw() {
...
boolean canUseAsync = draw(fullRedrawNeeded);
}

private boolean draw(boolean fullRedrawNeeded) {
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
//硬件绘制
...
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this, callback);
} else {
//软件绘制
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
...
}

向下调用到View.draw()

1
2
3
4
5
6
7
8
9
10
11
//View.java
public void draw(Canvas canvas) {
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
if (!dirtyOpaque) {
drawBackground(canvas);//绘制背景
}
if (!dirtyOpaque) onDraw(canvas);//绘制自身
dispatchDraw(canvas); //绘制子View
onDrawForeground(canvas);//绘制前景
}

关键在于是否持有PFLAG_DIRTY_OPAQUE标志,这个标志主要是在invalidate()打上的。

invalidate()通过设置PFLAG_INVALIDATEDPFLAG_DRAWING_CACHE_VALID标记,然后执行invalidateChild()通过层层向上调用parent.invalidateChildInParent()把需要重新绘制的区域传递上去,直到达到ViewRootImpl为止。最后调用到invalidateRectOnScreen()传入最终需要重新绘制的区域,开始执行绘制流程。

invalidate()会打上PFLAG_DIRTY_OPAQUE标记,只有这个标记才会执行onDraw()

两者区别

rquestLayout()invalidate()都可以触发整个绘制流程,但是触发measurelayoutdraw各条件都不同

  • measure过程触发:mPrivateFlags包含PFLAG_FORCE_LAYOUT
  • layout过程触发:mPrivateFlags包含PFLAG_LAYOUT_REQUIRED(measure执行后添加)或者位置发生变化
  • draw过程触发:mPrivateFlags包含PFLAG_DIRTY_OPAQUE

requestLayout()主要用来设置PFLAG_FORCE_LAYOUT标志以及设置mLayoutRequested=true,会执行到measure、layout过程,如果位置发生变化则可能执行draw过程。

invalidate()主要用来设置PFLAG_DIRTY_OPAQUE标志,可以在执行performTraversal()时,调用到performDraw()后到draw()时,可以进行绘制过程。

区别

同时调用

在某些情况下,需要requestLayoutinvalidate配合使用,得到最终的结果。

TextView举例:

TextView.setText()

TextView执行setText()后,TextView可能布局发生改变,也可能需要进行重绘。需要区分不同情况实现对应功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//TextView.java
private void setText(CharSequence text, //文本
BufferType type, //缓存类型 NORMAL 默认样式 SPANNABLE 自定义样式 EDITABLE 可以追加字符
boolean notifyBefore, //是否通知之前
int oldlen //旧文本长度
)
{
...
if (mLayout != null) {
checkForRelayout();
}
...
}

private void checkForRelayout() {
//TextView的宽度以及高度固定不变
if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT//宽度固定
|| (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
&& (mHint == null || mHintLayout != null)
&& (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
// Static width, so try making a new text layout.

int oldht = mLayout.getHeight();
int want = mLayout.getWidth();
int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();

/*
* No need to bring the text into view, since the size is not
* changing (unless we do the requestLayout(), in which case it
* will happen at measure).
*/

makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
false);

//非跑马灯格式
if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
// In a fixed-height view, so use our new text layout.
if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
&& mLayoutParams.height != LayoutParams.MATCH_PARENT) {//高度为>0的值
autoSizeText();
invalidate();
return;
}

// 除非主动设置高度,否则高度与行数相关,不会为0
if (mLayout.getHeight() == oldht
&& (mHintLayout == null || mHintLayout.getHeight() == oldht)) {//高度没有发生变化
autoSizeText();
invalidate();
return;
}
}

// We lose: the height has changed and we have a dynamic height.
// Request a new view layout using our new text layout.
requestLayout();
invalidate();
} else {
// Dynamic width, so we have no choice but to request a new
// view layout with a new text layout.
nullLayouts();
requestLayout();
invalidate();
}
}

根据上述源码分析:

TextView.setText()根据不同的情况会执行不同的重绘方法:

  • width!=wrap_content
    • 非跑马灯格式(mEllipsize!=MARQUEE) && (height > 0 || height == oldht/*高度没有变化*/):执行invalidate()
    • others:执行requestLayout()invalidate()
  • width=wrap_content:需要执行requestLayout()invalidate()

拓展

forceLayout()

标记当前View需要重新执行测量-布局-绘制流程。一般父ViewGroup调用requestLayout(),子View不会触发整套流程。

调用后,子View也可以执行整套流程。

1
2
3
4
5
6
7
//View.java   
public void forceLayout() {
if (mMeasureCache != null) mMeasureCache.clear();

mPrivateFlags |= PFLAG_FORCE_LAYOUT;//标记 执行measure
mPrivateFlags |= PFLAG_INVALIDATED;
}

forceLayout若要生效,需要直系父View调用requestLayout

include、merge、ViewStub作用以及实现

{% post_link include、merge-ViewStub相关%}

View的层级计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//实际形成一个二叉树 递归计算深度    
private int maxViewDeep(View view) {
if (!(view instanceof ViewGroup)) {
return 0;
}

ViewGroup vp = (ViewGroup) view;
if (vp.getChildCount() == 0) {
return 0;
}

int max = 0;
int count = vp.getChildCount();
for (int i = 0; i < count; i++) {
int deep = maxViewDeep(vp.getChildAt(i)) + 1;
if (deep > max) {
max = deep;
}
}
return max;
}

AsyncLayoutInflater异步加载

AsyncLayoutInflater

inflate()时,rootattachToRoot的结果源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//LayoutInflater.java
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
View result = root;

if (root != null) {
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
//将temp添加到rootView中
root.addView(temp, params);
}

// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
//attachToRoot:将View添加到RootView中,非就是直接返回解析的子View
result = temp;
}
return result;
}

根据源码分析到,rootattachToRoot会对infalte()结果产生影响以及实现代码会有差异

rootattachToRoot参数 表现 inflate()返回结果
root == nuill && attachToRoot == false/true 直接显示source加载的结果,而且设置的宽高属性也会失效 source加载后的View实例
root != null && attachToRoot == false 直接显示source加载的结果,且设置的宽高属性保持 source加载后的View实例
root != null && attachToRoot == true 直接显示root并且source已被add进去且设置的宽高属性保持 root

root不为null,无论attachToRoot,都会保持需要加载布局的宽高属性。root为null,无论attachToRoot,都不会持有原有的宽高属性。

MeasureSpec.UNSPECIFIED使用场景

UNSPECIFIED就是未指定的意思,在这个模式下 父控件不会干涉子控件的尺寸。

一般用于支持滚动的布局中,例如ScrollViewRecyclerView中。

在可滚动的ViewGroup中,不应该限制子View的尺寸。有可能子View会超出父布局的尺寸,在AT_MOST/EXACTLY都会对子View的尺寸进行限制。

ScrollView举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//ScrollView.java
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec)
{
ViewGroup.LayoutParams lp = child.getLayoutParams();

int childWidthMeasureSpec;
int childHeightMeasureSpec;

childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft
+ mPaddingRight, lp.width);
final int verticalPadding = mPaddingTop + mPaddingBottom;
//强制设置子View的测量模式为`UNSPECIFIED`
childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - verticalPadding),
MeasureSpec.UNSPECIFIED);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!