这是我参与「第四届青训营 」笔记创作活动的的第6天
UI的渲染
1、布局的加载
在Android中大部分的UI界面都是以xml文件的形式编写,然后通过setContentView() 来对布局进行加载,让我们进入源码学习。
首先进入onCreate() 方法:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
通过setContentView() 方法,进入AppCompatActivity类中的setContentView方法:
public class AppCompatActivity extends FragmentActivity implements AppCompatCallback,
TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
//.........
@Override
public void setContentView(@LayoutRes int layoutResID) {
initViewTreeOwners();
getDelegate().setContentView(layoutResID);
}
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
}
通过AppCompatDelegate.create()方法进入AppCompatDelegate类,create()用来新建一个AppCompatDelegate类的实现类AppCompatDelegateImpl
public abstract class AppCompatDelegate {
@NonNull
public static AppCompatDelegate create(@NonNull Activity activity,
@Nullable AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, callback);
}
}
然后进入AppCompatDelegateImpl的setContentView()方法
class AppCompatDelegateImpl extends AppCompatDelegate implements MenuBuilder.Callback, LayoutInflater.Factory2 {
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
//加载布局
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
}
最终通过LayoutInflater来加载了XML布局
2、布局的解析
从LayoutInflater开始,探索怎样解析布局,从inflate()方法开始分析
public abstract class LayoutInflater {
//...........省略
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) {
//省略。。。
XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
//省略。
//核心代码
rInflate(parser, root, inflaterContext, attrs, false);
}
}
进入rInflate()方法;
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;
//省略。。。。
//使用parser对象进行布局的解析,
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
//省略。。。。。
//根据解析的xml中的属性值,创建对应的View对象
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
rInflate()方法中的AttributeSet参数 AttributeSet是xml文件中元素属性的一个集合。其中提供了各种Api,供我们从已编译好的xml文件获取属性值,如getAttributeIntValue,getAttributeBooleanValue,getAttributeFloatValue等,会返回对应类型的属性值,传入的参数一般有两种形式,如下:
- getAttributeXXXValue(int index, XXX defaultValue):根据对应属性的索引获取对应的属性值,index取值范围在0~count-1之间,找不到返回defaultValue
- getAttributeXXXValue(String namespace, String attribute, XXX defaultValue):根据指定命名空间的属性名获取对应的属性值,找不到返回defaultValue
进入createViewFromTag方法中
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
//省略。。。。。。。。
//主要方法
View view = tryCreateView(parent, name, context, attrs);
return view;
}
进入tryCreateView方法中
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
return view;
}
从上段代码看出,view是由 mFactory2.onCreateView()方法返回的,而从前面代码可以看出AppCompatDelegateImpl类实现了LayoutInflater.Factory2接口,所以实现了onCreateView的方法,进入AppCompatDelegateImpl类的onCreateView方法
/**
* From {@link LayoutInflater.Factory2}.
*/
@SuppressWarnings("NullableProblems")
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return createView(parent, name, context, attrs);
}
/**
* From {@link LayoutInflater.Factory2}.
*/
@SuppressWarnings("NullableProblems")
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return onCreateView(null, name, context, attrs);
}
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
//省略
//核心
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
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
//省略
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(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);
backportAccessibilityAttributes(context, view, attrs);
}
return view;
}
我们在xml布局中写的控件,最终都会根据标识解析生成对应的View对象。
以下是流程的梳理