前言
說到View的繪制大部分人都能說上一兩句,但細節實在太多如果沒系統的去看很難吃透。近期我專門抽了一周多的時間讀了繪制相關的源碼,這裡準備用三篇部落格做一個系統的講述,目錄如下。
- View繪制源碼淺析(一)布局的加載
- View繪制源碼淺析(二)布局的測量、布局、繪制
- View繪制源碼淺析(三)
、requestLayout
與invalidate
三者的差別postInvalidate
本文的源碼基于API27。
疑問
布局加載最重要的就是
setContentView()
方法了,隻需要我們傳入一個布局id即可完成布局的加載,但實際上這裡是有幾個疑問的。
- 如何根據xml建立View的。
- 如何讀取xml中View相關屬性的。
- 建立的View添加到了哪。
接下來我們帶着這些問題再去看源碼,避免迷失方向。
setContentView()
我們先從
setContentView()
這個布局加載的入口開始,看看究竟如何加載布局的。
//MainActivity.java
public class MainActivity extends AppCompatActivity {//繼承appCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);//布局加載的入口
}
}
//AppCompatActivity.java
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);//拿到Activity的委托對象調用setContentView()
}
//AppCompatActivity.java
@NonNull
public AppCompatDelegate getDelegate() {//擷取Activity委托對象
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
//AppCompatDelegate.java
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {//建立Activity委托對象
return create(activity, activity.getWindow(), callback);//這裡将activity.getWindow()傳入。
}
//AppCompatDelegate.java
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {//根據不同的版本建立不同的Activity委托對象
if (Build.VERSION.SDK_INT >= 24) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (Build.VERSION.SDK_INT >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else {
return new AppCompatDelegateImplV14(context, window, callback);
}
}
//AppCompatDelegateImplV9.java
//最終是調到v9的setContentView方法
@Override
public void setContentView(int resId) {
ensureSubDecor();//確定SubDecor相關布局初始化完成
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);//找到id為content的view
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);//通過LayoutInflater直接把我們的布局添加到id為content的布局上
mOriginalWindowCallback.onContentChanged();
}
由于繼承的是
appCompatActivity
這個相容的
Activity
是以是根據不同的api版本建立不同的
AppCompatDelegate
實作類以相容老邏輯。
setContentView()
最終是調到了
AppCompatDelegateImplV9
的
setContentView()
,接下來具體實作分為兩步。
- 通過
方法確定ensureSubDecor()
相關布局初始化完成。SubDecor
- 找到
中id為content的布局,将我們自己的布局inflater到content上。SubDecor
這裡說明下,
SubDecor
不是
DecorView
,隻是一個變量名為
subDecor
的
ViewGroup
不過這裡充當
DecorView
的角色,不要混淆了。
這裡先說第一步
ensureSubDecor()
//AppCompatDelegateImplV9.java
private void ensureSubDecor() {//確定SubDecor的建立
if (!mSubDecorInstalled) {//如果沒有建立SubDecor
mSubDecor = createSubDecor();//建立SubDecor
...
}
}
private ViewGroup createSubDecor() {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);//拿到AppCompat相關的主題屬性
//根據主題中的屬性執行對應的Feature方法
if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
requestWindowFeature(Window.FEATURE_NO_TITLE);//我們比較熟悉的FEATURE_NO_TITLE Feature
} else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
}
mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
a.recycle();
mWindow.getDecorView();//確定DecorView的建立
final LayoutInflater inflater = LayoutInflater.from(mContext);//用來填充SubDecor的inflater
ViewGroup subDecor = null;//subDecor布局
//接下來是根據主題屬性初始化不同的subDecor布局
if (!mWindowNoTitle) {
if (mIsFloating) {
// If we're floating, inflate the dialog title decor
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);
// Floating windows can never have an action bar, reset the flags
mHasActionBar = mOverlayActionBar = false;
} else if (mHasActionBar) {
/**
* This needs some explanation. As we can not use the android:theme attribute
* pre-L, we emulate it by manually creating a LayoutInflater using a
* ContextThemeWrapper pointing to actionBarTheme.
*/
TypedValue outValue = new TypedValue();
mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
Context themedContext;
if (outValue.resourceId != 0) {
themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
} else {
themedContext = mContext;
}
// Now inflate the view using the themed context and set it as the content view
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
mDecorContentParent = (DecorContentParent) subDecor
.findViewById(R.id.decor_content_parent);
mDecorContentParent.setWindowCallback(getWindowCallback());
/**
* Propagate features to DecorContentParent
*/
if (mOverlayActionBar) {
mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
if (mFeatureProgress) {
mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
}
if (mFeatureIndeterminateProgress) {
mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
}
}
} else {
if (mOverlayActionMode) {
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_screen_simple_overlay_action_mode, null);
} else {
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
}
if (Build.VERSION.SDK_INT >= 21) {
// If we're running on L or above, we can rely on ViewCompat's
// setOnApplyWindowInsetsListener
ViewCompat.setOnApplyWindowInsetsListener(subDecor,
new OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
final int top = insets.getSystemWindowInsetTop();
final int newTop = updateStatusGuard(top);
if (top != newTop) {
insets = insets.replaceSystemWindowInsets(
insets.getSystemWindowInsetLeft(),
newTop,
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom());
}
// Now apply the insets on our view
return ViewCompat.onApplyWindowInsets(v, insets);
}
});
} else {
// Else, we need to use our own FitWindowsViewGroup handling
((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
new FitWindowsViewGroup.OnFitSystemWindowsListener() {
@Override
public void onFitSystemWindows(Rect insets) {
insets.top = updateStatusGuard(insets.top);
}
});
}
}
if (subDecor == null) {//檢查SubDecor是否建立
throw new IllegalArgumentException(
"AppCompat does not support the current theme features: { "
+ "windowActionBar: " + mHasActionBar
+ ", windowActionBarOverlay: "+ mOverlayActionBar
+ ", android:windowIsFloating: " + mIsFloating
+ ", windowActionModeOverlay: " + mOverlayActionMode
+ ", windowNoTitle: " + mWindowNoTitle
+ " }");
}
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);//找到subDecor中id為action_bar_activity_content的布局
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);//找到PhoneWindow中id為content的布局
if (windowContentView != null) {
while (windowContentView.getChildCount() > 0) {//将windowContentView的子布局全部添加到subDecor中id為action_bar_activity_content的布局上
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
windowContentView.setId(View.NO_ID);//清除PhoneWindow中id為content的布局id
contentView.setId(android.R.id.content);//給subDecor中id為action_bar_activity_content的布局設定上新的id為content以假亂真
if (windowContentView instanceof FrameLayout) {
((FrameLayout) windowContentView).setForeground(null);
}
}
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);//将subDecor設定到window上
return subDecor;
}
這裡先拿到主題中的屬性,然後根據主題中的屬性設定對應的Feature,并根據條件建立對應的
subDecor
。接下來拿到
PhoneWindow
中id為
content
的View,把它的子布局全部添加到subDecor中id為
action_bar_activity_content
的布局上,然後将
windowContentView
的id移除,給subDecor中id為
action_bar_activity_content
的布局設定上新的id為
content
以假亂真充當
ContentView
的角色,最後将
SubDecor
通過
mWindow.setContentView(subDecor)
設定到window上。
那麼經過
ensureSubDecor()
方法後我們就完成了
DecorView
和
SubDecor
的初始化并通過
mWindow.setContentView(subDecor)
将
SubDecor
添加到了
DecorView
上。完成了
SubDecor
和
DecorView
的關聯。
在回到我們之前的
setContentView()
//AppCompatDelegateImplV9.java
//最終是調到v9的setContentView方法
@Override
public void setContentView(int resId) {
ensureSubDecor();//確定SubDecor相關布局初始化完成
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);//找到id為content的view
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);//通過LayoutInflater直接把我們的布局添加到id為content的布局上
mOriginalWindowCallback.onContentChanged();
}
完成
SubDecor
初始化後,我們通過
mSubDecor.findViewById(android.R.id.content)
找到
contentParent
,然後直接
LayoutInflater.from(mContext).inflate(resId, contentParent)
将布局添加到了
contentParent
上完成了布局的添加。
那麼對于第一和第二個問題則必須在
LayoutInflater.inflate()
中尋找答案了,而第三個問題我們已經可以回答了
建立的View添加到了哪?
答:添加到了id為
的view上。
android.R.id.content
LayoutInflater.inflate()
接下來我們看下
inflate()
是如何将我們的布局添加到id為content的view上的。
//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 XmlResourceParser parser = res.getLayout(resource);//根據資源id建立解析器
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
...
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;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {//拿到第一個節點
// Empty
}
if (type != XmlPullParser.START_TAG) {//如果不是開始标簽報錯
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();//拿到節點名字
if (TAG_MERGE.equals(name)) {//merge單獨處理
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {//通用邏輯處理
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);//建立xml中根布局
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);//根據父布局建立xml中根布局的lp,因為自己的lp是跟父布局有關的。
if (!attachToRoot) {//當滿足root不為null并且attachToRoot為false則直接将lp設定到temp上
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);//inflate temp的子布局,具體實作就是遞歸的把view添加到temp上
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {//如果root不為null 并且 attachToRoot為true則直接add到root上
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) {
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;
}
}
它是先通過
res.getLayout(resource)
從布局id拿到解析器對象
XmlResourceParser
,然後通過
createViewFromTag()
方法建立根布局,再
root.generateLayoutParams(attrs)
建立根布局的lp,之後根據
root
和
attachToRoot
這兩個條件判斷是在
addView()
還是在
setLayoutParams()
給根布局加上lp,這中間還有一步
rInflateChildren()
将xml中的子布局依次添加到根布局上。
這中間有些細節值得深究下
-
如何建立布局的(解答第一個問題)createViewFromTag()
-
如何讀取lp相關屬性的(解答第二個問題)root.generateLayoutParams(attrs)
我們先看
root.generateLayoutParams(attrs)
,之是以要這樣是因為每個view的lp的建立都是跟父布局有關的,比如root是
LinearLayout
那麼建立的就是
LinearLayout.LayoutParams
并初始化
LinearLayout
獨有的
weight
和
gravity
屬性,而如果root是
RelativeLayout
那麼建立的是
RelativeLayout.LayoutParams
并初始化跟各個布局的限制關系。
這裡我們可以看下
LinearLayout.generateLayoutParams()
方法
//LinearLayout.java
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new LinearLayout.LayoutParams(getContext(), attrs);
}
//LinearLayout.LayoutParams 内部類
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);//這裡還調用了父類MarginLayoutParams的構造方
TypedArray a =
c.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout_Layout);
weight = a.getFloat(com.android.internal.R.styleable.LinearLayout_Layout_layout_weight, 0);//擷取xml中weight屬性
gravity = a.getInt(com.android.internal.R.styleable.LinearLayout_Layout_layout_gravity, -1);//擷取layout_gravity屬性
a.recycle();
}
}
//ViewGroup.MarginLayoutParams 内部類 初始化margin屬性
public static class MarginLayoutParams extends ViewGroup.LayoutParams {
public MarginLayoutParams(Context c, AttributeSet attrs) {
super();
TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout);
setBaseAttributes(a,
R.styleable.ViewGroup_MarginLayout_layout_width,
R.styleable.ViewGroup_MarginLayout_layout_height);//調用父類ViewGroup.LayoutParams的setBaseAttributes()讀取最基礎的寬高屬性
//下面就是讀取xml各種margin屬性
int margin = a.getDimensionPixelSize(
com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1);
if (margin >= 0) {//如果margin大于0則直接用margin屬性
leftMargin = margin;
topMargin = margin;
rightMargin= margin;
bottomMargin = margin;
} else {//分别讀取left、top、right、bottom的margin屬性
int horizontalMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1);
int verticalMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginVertical, -1);
if (horizontalMargin >= 0) {
leftMargin = horizontalMargin;
rightMargin = horizontalMargin;
} else {
leftMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginLeft,
UNDEFINED_MARGIN);
if (leftMargin == UNDEFINED_MARGIN) {
mMarginFlags |= LEFT_MARGIN_UNDEFINED_MASK;
leftMargin = DEFAULT_MARGIN_RESOLVED;
}
rightMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginRight,
UNDEFINED_MARGIN);
if (rightMargin == UNDEFINED_MARGIN) {
mMarginFlags |= RIGHT_MARGIN_UNDEFINED_MASK;
rightMargin = DEFAULT_MARGIN_RESOLVED;
}
}
startMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginStart,
DEFAULT_MARGIN_RELATIVE);
endMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginEnd,
DEFAULT_MARGIN_RELATIVE);
if (verticalMargin >= 0) {
topMargin = verticalMargin;
bottomMargin = verticalMargin;
} else {
topMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginTop,
DEFAULT_MARGIN_RESOLVED);
bottomMargin = a.getDimensionPixelSize(
R.styleable.ViewGroup_MarginLayout_layout_marginBottom,
DEFAULT_MARGIN_RESOLVED);
}
if (isMarginRelative()) {
mMarginFlags |= NEED_RESOLUTION_MASK;
}
}
final boolean hasRtlSupport = c.getApplicationInfo().hasRtlSupport();
final int targetSdkVersion = c.getApplicationInfo().targetSdkVersion;
if (targetSdkVersion < JELLY_BEAN_MR1 || !hasRtlSupport) {
mMarginFlags |= RTL_COMPATIBILITY_MODE_MASK;
}
// Layout direction is LTR by default
mMarginFlags |= LAYOUT_DIRECTION_LTR;
a.recycle();
}
}
//ViewGroup.LayoutParams 内部類
public static class LayoutParams {
protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) {//讀取寬高
width = a.getLayoutDimension(widthAttr, "layout_width");
height = a.getLayoutDimension(heightAttr, "layout_height");
}
}
可以看到
LinearLayout.LayoutParams
有兩層繼承關系,
LinearLayout.LayoutParams
負責拿到
LinearLayout
的特有屬性,
ViewGroup.MarginLayoutParams
負責拿到
margin
屬性,
ViewGroup.LayoutParams
負責拿到寬高。一般的
ViewGroup
基本都是實作自己的lp然後繼承
ViewGroup.MarginLayoutParams
。
這裡可以回答我們第二個問題
如何讀取xml中View相關屬性的?
通過
根據root不同建立不同的LayoutParams讀取xml中相關屬性
root.generateLayoutParams(attrs)
接下來看
createViewFromTag()
如何建立布局的
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);//調用建立重載方法
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
try {
View view;
//優先嘗試通過Factory建立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建立,一般情況下是為null的
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
//上面兩種方式都建立失敗了
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {//如果view的名字中不帶'.',其實就是系統控件,類似TextView這種我們在xml寫的時候不是全類名。
view = onCreateView(parent, name, attrs);//建立系統控件
} else {//全類名控件的建立分支,一般都是自定義控件
view = createView(name, null, attrs);//建立自定義控件
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}
代碼中可以看出布局的建立是先用
Factory2
和
Factory
,如果建立失敗在通過
mPrivateFactory
,如果還是失敗則通過
onCreateView()
或者
createView()
方法建立。
Factory
我們稍後說,
mPrivateFactory
一般為null我們暫且忽略。
我們先看
onCreateView()
或者
createView()
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {//建立系統控件
return onCreateView(name, attrs);
}
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);//給系統控件加上全類名字首"android.view."
}
//最終都是通過createView建立View
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);//擷取緩存的Constructor
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);//拿到name對應的class
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
constructor = clazz.getConstructor(mConstructorSignature);//拿到View的構造方法
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;
final View view = constructor.newInstance(args);//通過反射建立View
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;
} catch (NoSuchMethodException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (ClassCastException e) {
// If loaded class is not a View subclass
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
final InflateException ie = new InflateException(
attrs.getPositionDescription() + ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
可以看到如果
Factory
和
Factory2
建立View失敗則會通過反射的方式建立View。
那麼
Factory
和
Factory2
是在何時設定的呢,其實是在Activity的
onCreate()
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();//拿到委托對象調用installViewFactory()設定factory
super.onCreate(savedInstanceState);
}
}
最後是調到
AppCompatDelegateImplV9
的
installViewFactory()
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);//拿到LayoutInflater
if (layoutInflater.getFactory() == null) {//如果Factory為空
LayoutInflaterCompat.setFactory2(layoutInflater, this);//設定Fractory2
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
這裡有人可能會問明明是
setFactory2()
為何要判斷
layoutInflater.getFactory() == null
,這裡解釋下,無論
Factory
還是
Factory2
都隻是提供給我們一個可以根據xml中标簽名字生成View的接口,而
Factory2
是繼承的
Factory
,隻是比
Factory
多一個方法,支援建立View時添加父布局參數,并且因為
Factory2
是繼承
Factory
是以無論是
setFactory2()
或者
setFactory()
都會給Factory指派,是以我們隻需要判斷
layoutInflater.getFactory() == null
就可以知道是否設定過。并且
setFactory()
也隻能設定一次,多次次設定會報錯的。
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);//多一個View parent參數的方法
}
public final Factory getFactory() {
return mFactory;
}
public void setFactory2(Factory2 factory) {
if (mFactorySet) {//判斷是否設定過了
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;//設定标記為置為true
if (mFactory == null) {
mFactory = mFactory2 = factory;//給mFactory和mFactory2都指派
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
public void setFactory(Factory factory) {
if (mFactorySet) {//判斷是否設定過了
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;//設定标記為置為true
if (mFactory == null) {
mFactory = factory;//給mFactory指派
} else {
mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
}
}
從代碼上也印證了上面的結論。接下來回到
AppCompatDelegateImplV9
的
installViewFactory()
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);//拿到LayoutInflater
if (layoutInflater.getFactory() == null) {//如果Factory為空
LayoutInflaterCompat.setFactory2(layoutInflater, this);//設定Fractory2
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
//LayoutInflaterCompat.setFactory2()
public static void setFactory2(
@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
IMPL.setFactory2(inflater, factory);
}
static final LayoutInflaterCompatBaseImpl IMPL;
static {
if (Build.VERSION.SDK_INT >= 21) {
IMPL = new LayoutInflaterCompatApi21Impl();//impl具體實作類
} else {
IMPL = new LayoutInflaterCompatBaseImpl();
}
}
@RequiresApi(21)
static class LayoutInflaterCompatApi21Impl extends LayoutInflaterCompatBaseImpl {
@SuppressWarnings("deprecation")
@Override
public void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
inflater.setFactory2(factory != null ? new Factory2Wrapper(factory) : null);
}
@Override
public void setFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
inflater.setFactory2(factory);//最終還是調的LayoutInflater.setFactory2()
}
}
可以看到
LayoutInflaterCompat.setFactory2(layoutInflater, this)
傳入的第二個參數
Factory2
是this,那麼我們看下
AppCompatDelegateImplV9
如何實作的。
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
final View view = callActivityOnCreateView(parent, name, context, attrs);//先調用activity的onCreateView()一般情況下我們都沒實作該方法是以傳回值為null
if (view != null) {
return view;
}
return createView(parent, name, context, attrs);//是以建立View的方法在此
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {//調用onCreateView(View parent, String name, Context context, AttributeSet attrs)
return onCreateView(null, name, context, attrs);
}
我們在看到
createView()
方法
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
//根據各種條件建立AppCompatViewInflater
if (mAppCompatViewInflater == null) {
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
String viewInflaterClassName =
a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
if ((viewInflaterClassName == null)
|| AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
mAppCompatViewInflater = new AppCompatViewInflater();//建立AppCompatViewInflater
} else {
try {
Class viewInflaterClass = Class.forName(viewInflaterClassName);
mAppCompatViewInflater =
(AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
.newInstance();//建立AppCompatViewInflater
} catch (Throwable t) {
Log.i(TAG, "Failed to instantiate custom view inflater "
+ viewInflaterClassName + ". Falling back to default.", t);
mAppCompatViewInflater = new AppCompatViewInflater();//建立AppCompatViewInflater
}
}
}
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
true, /* Read read app:theme as a fallback at all times for legacy reasons */
VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
);//最終調用mAppCompatViewInflater.createView()建立View
}
最終建立View是通過
AppCompatViewInflater
的
createView()
方法
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
//拿到context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {//根據标簽名字建立對應的View
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
case "Spinner":
view = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case "ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case "RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckedTextView":
view = createCheckedTextView(context, attrs);
verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
view = createAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
view = createMultiAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "RatingBar":
view = createRatingBar(context, attrs);
verifyNotNull(view, name);
break;
case "SeekBar":
view = createSeekBar(context, attrs);
verifyNotNull(view, name);
break;
default:
// The fallback that allows extending class to take over view inflation
// for other tags. Note that we don't check that the result is not-null.
// That allows the custom inflater path to fall back on the default one
// later in this method.
view = createView(context, name, attrs);
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check its android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
接下來我們看下
createTextView()
方法
@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
看發現了什麼,對于xml中的TextView實際建立的是
AppCompatTextView
,對沒錯這就是繼承
AppCompatActivity
後他對我們xml中部分布局做了相容。
到這裡View的建立其實就說完了,這裡可以回答第一個問題了。
如何根據xml建立View的?
View的建立是在
方法,依次先用
LayoutInflater.createViewFromTag()
和
Factory2
建立,如果建立失敗在通過
Factory
建立,如果還是失敗則通過
mPrivateFactory
或者
onCreateView()
方法建立。
createView()
這裡我們可以試着自己實作的一個
Factory2
然後在
super.onCreate()
之前設定給
LayoutInflater
将TextView标簽解析為一個
Button
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
if (LayoutInflater.from(this).getFactory() == null) {
LayoutInflater.from(this).setFactory2(new LayoutInflater.Factory2() {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View view = null;
switch (name) {
case "TextView":
view = new Button(context, attrs);
break;
}
return view;
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return null;
}
});
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>
可以看到xml中TextView變成了一個Button。
總結
整體流程用一句話來說就是
setContentView
完成
DecorView
相關布局初始化并将我們的布局通過
LayoutInflater.inflate()
方法添加到id為Content的ViewGroup上。
具體細節的話就是我們前面那個三個問題
-
建立的View添加到了哪?
答:添加到了id為
的view上。android.R.id.content
-
如何讀取xml中View相關屬性的?
通過
根據root不同建立不同的LayoutParams讀取xml中相關屬性root.generateLayoutParams(attrs)
-
如何根據xml建立View的?
View的建立是在
方法,依次先用LayoutInflater.createViewFromTag()
和Factory2
建立,如果建立失敗在通過Factory
建立,如果還是失敗則通過mPrivateFactory
或者onCreateView()
方法建立。createView()