总述
Android界面上的每一个控件都是一个个View,但是Android也提供了通过xml文件来进行布局控制,那么xml布局文件如何转成最终的View的呢?转换利器就是LayoutInflater。
LayoutInflater的获取
1、通过Activity实例的getLayoutInflater()方法
getLayoutInflater()方法源码:
public LayoutInflater getLayoutInflater() {
return getWindow().getLayoutInflater();
}
其中getWindow()方法直接就是返回mWindow属性,源码:
public Window getWindow() {
return mWindow;
}
mWindow初始化代码位于attach()方法:
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) {
// 其他代码
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
// 其他代码
}
通过调用PolicyManager.makeNewWindow()方法初始化,我们就看看PolicyManager的makeNewWindow源码吧
位于./frameworks/base/core/java/com/android/internal/policy/PolicyManager.java
public static Window makeNewWindow(Context context) {
return sPolicy.makeNewWindow(context);
}
通过调用sPolicy.makeNewWindow()方法实现,sPolicy初始化代码:
private static final StringPOLICY_IMPL_CLASS_NAME = "com.android.internal.policy.impl.Policy";
private static final IPolicy sPolicy;
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
sPolicy = (IPolicy)policyClass.newInstance();
也就是通过反射获取一个com.android.internal.policy.impl.Policy实例,那我们就去看看那个类的makeNewWindow吧
位于./frameworks/base/policy/src/com/android/internal/policy/impl/Policy.java
public Window makeNewWindow(Context context) {
return new PhoneWindow(context);
}
直接创建一个PhoneWindow方法返回,那我们就去看看PhoneWindow里面怎么实现getLayoutInflater的吧
位于:./frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java
public LayoutInflater getLayoutInflater() {
return mLayoutInflater;
}
其中mLayoutInflater初始化代码:
public PhoneWindow(Context context) {
// 其他代码
mLayoutInflater = LayoutInflater.from(context);
}
也就是最终通过调用LayoutInflater.from(Context)实现
这也就引入了本文要说的第二种获取LayoutInflater方式
2、通过LayoutInflater.from(Context)获取
LayoutInflater.from()方法源码:
public static LayoutInflater from(Context context) {
LayoutInflater layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (layoutInflater == null) {
throw newAssertionError("LayoutInflater not found.");
}
return layoutInflater;
}
通过Context实例的getSystemService() 方法实现
这也就引入了本文要说的第三种获取LayoutInflater方式
3、通过Context实例的getSystemService(String)获取
典型代码就是这样:
LayoutInflater layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
这个方法最终会调用到ContextImpl这个类的getSystemService方法去
位于:./frameworks/base/core/java/android/app/ContextImpl.java
public Object getSystemService(String name) {
ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
return fetcher == null ? null : fetcher.getService(this);
}
其中SYSTEM_SERVICE_MAP是一个常量属性:
private static final HashMap<String,ServiceFetcher> SYSTEM_SERVICE_MAP = new HashMap<String,ServiceFetcher>();
而ServiceFetcher则是ContextImpl的一个静态内部类,其中getService则是一个懒汉式加载方式
static class ServiceFetcher {
int mContextCacheIndex = -1;
public Object getService(ContextImpl ctx) {
ArrayList<Object> cache =ctx.mServiceCache;
Object service;
synchronized (cache) {
if (cache.size() == 0) {
for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
cache.add(null);
}
} else {
service =cache.get(mContextCacheIndex);
if (service != null) {
return service;
}
}
service = createService(ctx);
cache.set(mContextCacheIndex,service);
return service;
}
}
public Object createService(ContextImpl ctx) {
throw newRuntimeException("Not implemented");
}
}
其中createService方法将会在子类中实现
接着我们看看SYSTEM_SERVICE_MAP值的初始化,
通过以下静态方法注册完成:
private static void registerService(String serviceName, ServiceFetcher fetcher) {
if (!(fetcher instanceof StaticServiceFetcher)) {
fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
}
SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
}
而这个注册方法则在静态块里面调用,也就是ContextImpl类加载的时候调用:
static {
// 其他服务注册,包括账号服务,Activity管理服务,闹钟服务等
registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher(){
public ObjectcreateService(ContextImpl ctx) {
return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
}});
// 其他服务注册,包括USB服务,Wifi服务,以太网服务等
}
这里使用了匿名内部类重写ServiceFetcher 的createService方法
实现则是通过调用PolicyManager.makeNewLayoutInflater()获取LayoutInflater,其中PolicyManager类我们之前看过了
位于./frameworks/base/core/java/com/android/internal/policy/PolicyManager.java中
我们就去看看makeNewLayoutInflater方法的实现:
public static LayoutInflater makeNewLayoutInflater(Context context) {
return sPolicy.makeNewLayoutInflater(context);
}
同样sPolicy就是Policy的一个实例,我们之前也看过了,Policy代码
位于./frameworks/base/policy/src/com/android/internal/policy/impl/Policy.java
其中makeNewLayoutInflater方法的实现:
public LayoutInflater makeNewLayoutInflater(Context context) {
return new PhoneLayoutInflater(context);
}
这里直接创建一个PhoneLayoutInflater实例返回,那我们就去看看PhoneLayoutInflater代码
位于:./frameworks/base/policy/src/com/android/internal/policy/impl/PhoneLayoutInflater.java
public class PhoneLayoutInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
// 构造方法略过
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){
}
}
return super.onCreateView(name, attrs);
}
public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
}
我们详细看看onCreateView方法
这个方法第一个参数就是View的类名;第二个参数就是View的属性集合
实现步骤:
a、依次将sClassPrefixList的每一项当做前缀,调用createView创建View
b、创建成功直接返回,创建失败就调用父类的onCreateView创建
分析:
这里会尝试将”android.widget.”, “android.webkit.”, “android.app.”这三个字符串作为前缀尝试创建View
我们在xml布局文件中使用控件的时候,自定义控件需要写出控件全名(包名+类名),而系统内置控件在只要写出类名就行了,
其实底下实现就是尝试将系统前缀加上去,然后看看能不能创建出这样的View,细心的小伙伴是不是发现还有一个系统的包没有包含进去呢,
就是”android.view.”这个包,这个我们后面篇幅中将会讲到在什么时候添加的
到这里,我们已经将LayoutInflater的三种获取方式都讲完了。接着就去看看LayoutInflater这个类
LayoutInflater类
1、xml解析思路
a、解析xml,通过
递归方式
将里面的标签名和属性以及属性值获取出来
b、适当加工之后就就调用View的创建工厂创建View
2、view的创建工厂Factory和Factory2
LayoutInflater定义了两个创建View的工厂接口(Factory和Factory2),用于实现者实现通过类名和属性集合创建对应的View
源码:
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);
}
当然LayoutInflater也有对这两个工厂进行了默认实现,就是通过反射机制创建View的
3、LayoutInflater的属性
这里我就分析一下比较重要的几个,其他读者可以通过源码自己分析
/** 这个属性是一个长度为2的Object数组,
* 使用的时候第一个元素将会是Context实例,第二元素将会是AttributeSet实例,
* 对应于当前要创建的View的构造参数 **/
final Object[] mConstructorArgs = newObject[2];
/** 这个属性是一个长度为2的Class数组,
* 反射过程中将会获取参数类型是这个数组的两个元素的View的构造方法 **/
static final Class<?>[]mConstructorSignature = new Class[] { Context.class, AttributeSet.class };
/** 这个属性用于缓存已经获取到的构造方法,以便下一次使用 **/
private static final HashMap<String,Constructor<? extends View>> sConstructorMap = new HashMap<String,Constructor<? extends View>>();
/** 接下去的4个属性就是xml布局文件中支持的4中特殊标签,下面我将一一分析其含义和使用的注意事项 **/
private static final String TAG_MERGE ="merge";
private static final String TAG_INCLUDE ="include";
private static final String TAG_1995 ="blink";
private static final StringTAG_REQUEST_FOCUS = "requestFocus";
3.1、merge标签
该标签可以将它的直接子View添加到它的父View上去,也就是减少布局层次,优化布局
注意:该标签必须位于布局文件根结点
3.2、include标签
该标签可以引用另一个布局文件,达到布局文件复用效果
注意:该标签不能位于布局文件的根结点
3.3、blink标签
这个标签比较少用到,使用这个标签的的话,这个标签下面的所有子View(直接子View以及子View的子View等)将会出现闪烁效果
注意:闪烁周期为1秒(显示0.5秒,隐藏0.5秒)
3.4、requestFocus标签
这个标签就是会为其所在的View调用一下requestFocus()方法以请求获取焦点
4、LayoutInflater解析流程
解析api
a、public View inflate(int resource, ViewGroup root)
b、public View inflate(XmlPullParser parser, ViewGroup root)
c、public View inflate(int resource, ViewGroup root, boolean attachToRoot)
d、public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
其中前3个最终都是调用第4个实现的,那我们就看看第4个吧
4.1、inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
参数:parser :描述xml布局文件的xml解析实例
root:父控件,用于确定xml布局的根的LayoutParams
attachToRoot :是否将布局根控件添加到root控件上去
返回值:如果attachToRoot参数为true,那么将会返回root参数;否则返回xml文件的根控件
源码:
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
/** 系统用于跟踪记录,跳过 **/
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
/** 从xml中获取标签属性引用 **/
final AttributeSet attrs = Xml.asAttributeSet(parser);
/** 接下去2行,缓存当前的mConstructorArgs[0],设置新的context **/
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
/** 定义返回值,默认为root参数 **/
View result = root;
try {
int type;
/** 查找xml起始位置 **/
while ((type = parser.next())!= XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
}
if (type !=XmlPullParser.START_TAG) {
/** 找不到起始位置,抛出InflateException异常 **/
throw newInflateException(parser.getPositionDescription() + ": No start tagfound!");
}
/** 获取当前标签名 **/
final String name =parser.getName();
if (TAG_MERGE.equals(name)) {
if (root == null ||!attachToRoot) {
/** 如果是marge标签,但是root参数为空或者参数指定不添加到root上,则抛出异常 **/
throw newInflateException("<merge /> can be used only with a valid " +"ViewGroup root and attachToRoot=true");
}
/** 递归解析子控件,将root作为直接子空间的父控件(具体下文解析,这里当做疑问1) **/
rInflate(parser, root,attrs, false);
} else {
/** 以下将解析根布局,存储在temp里面 **/
View temp;
if (TAG_1995.equals(name)){
/** 根布局为blink标签,创建一个BlinkLayout(具体下文解析,这里当做疑问2) **/
temp = new BlinkLayout(mContext, attrs);
} else {
/** 创建View作为根布局(具体下文解析,这里当做疑问3)**/
temp =createViewFromTag(root, name, attrs);
}
ViewGroup.LayoutParams params = null;
if (root != null) {
/** 如果root不为空,则通过root类型解析属性获取对应的LayoutParams**/
params =root.generateLayoutParams(attrs);
if (!attachToRoot) {
/** 如果不添加到root上去,则将LayoutParams设置到根布局上去 **/
temp.setLayoutParams(params);
}
}
/** 同样递归解析子view(同疑惑1) **/
rInflate(parser, temp, attrs, true);
if (root != null &&attachToRoot) {
/** root不为空且需要添加到root上去则直接将根布局添加到root上去 **/
root.addView(temp,params);
}
if (root == null ||!attachToRoot) {
/** root为空或者不添加到root上去则更新返回值为根布局 **/
result = temp;
}
}
} catch (XmlPullParserException e){
// 异常处理略过
} finally {
/** 还原mConstructorArgs[0],并释放mConstructorArgs[1]**/
mConstructorArgs[0] =lastContext;
mConstructorArgs[1] = null;
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
return result;
}
}
上面代码主要就是对要返回这个View(布局文件的根View或者root参数)进行解析、校验等
4.2、疑问1:rInflate(XmlPullParser parser, View parent,final AttributeSet attrs, boolean finishInflate)
这个方法用于递归解析xml
参数:parser:为描述接下去要解析的xml布局文件的xml解析实例
parent:父控件,接下去的一层将添加到这个控件上去
attrs:属性集合
finishInflate:是否通知parent 参数实例解析完毕
源码:
void rInflate(XmlPullParser parser,View parent, final AttributeSet attrs, boolean finishInflate) throws XmlPullParserException,IOException {
final int depth = parser.getDepth();
int type;
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)){
/** 解析requestFocus标签(具体下文解析,这里当做疑问4) **/
parseRequestFocus(parser,parent);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
/** include标签位于根结点,抛出InflateException异常 **/
throw newInflateException("<include /> cannot be the root element");
}
/** 解析include标签(具体下文解析,这里当做疑问5) **/
parseInclude(parser, parent,attrs);
} else if (TAG_MERGE.equals(name)){
/** marge 标签不在根结点,抛出InflateException异常 **/
throw newInflateException("<merge /> must be the root element");
} else if (TAG_1995.equals(name)) {
/** 解析blink标签,解析之后继续递归解析,并添加到父控件中去 **/
final View view = newBlinkLayout(mContext, attrs);
final ViewGroup viewGroup = (ViewGroup)parent;
final ViewGroup.LayoutParamsparams = viewGroup.generateLayoutParams(attrs);
/** 解析blink标签 **/
rInflate(parser, view, attrs,true);
viewGroup.addView(view,params);
} else {
/** 解析其他标签,解析之后继续递归解析,并添加到父控件中去 **/
final View view =createViewFromTag(parent, name, attrs);
final ViewGroup viewGroup =(ViewGroup) parent;
final ViewGroup.LayoutParamsparams = viewGroup.generateLayoutParams(attrs);
rInflate(parser, view, attrs,true);
viewGroup.addView(view,params);
}
}
/** finishInflate为true,就调用parent的onFinishInflate方法通知该节点解析完毕 **/
if (finishInflate)parent.onFinishInflate();
}
4.3、疑问2:BlinkLayout
这个类继承于FrameLayout,并利用Handler在onAttachedToWindow定时发消息控制自己的可见性,在onDetachedFromWindow的时候取消定时器
源码比较简单我就不一一注释了:
private static class BlinkLayout extends FrameLayout {
private static final int MESSAGE_BLINK= 0x42;
private static final int BLINK_DELAY = 500;
private boolean mBlink;
private boolean mBlinkState;
private final Handler mHandler;
public BlinkLayout(Context context,AttributeSet attrs) {
super(context, attrs);
mHandler = new Handler(newHandler.Callback() {
public booleanhandleMessage(Message msg) {
if (msg.what ==MESSAGE_BLINK) {
if (mBlink) {
mBlinkState =!mBlinkState;
makeBlink();
}
invalidate();
return true;
}
return false;
}
});
}
private void makeBlink() {
Message message =mHandler.obtainMessage(MESSAGE_BLINK);
mHandler.sendMessageDelayed(message, BLINK_DELAY);
}
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mBlink = true;
mBlinkState = true;
makeBlink();
}
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mBlink = false;
mBlinkState = true;
mHandler.removeMessages(MESSAGE_BLINK);
}
protected void dispatchDraw(Canvascanvas) {
if (mBlinkState) {
super.dispatchDraw(canvas);
}
}
}
4.4、疑问3:View createViewFromTag(View parent, String name, AttributeSet attrs)
这个方法用于创建具体的View
参数:parent:父控件,接下去的一层将添加到这个控件上去
name:标签名,可能是View的全名(自定义View),也可能是View的类名(系统内置控件)
attrs:属性集合
返回值:所创建的View
源码:
View createViewFromTag(View parent, String name, AttributeSet attrs) {
if (name.equals("view")) {
/** view标签,则尝试获取class属性作为类名 **/
name =attrs.getAttributeValue(null, "class");
}
try {
View view;
/** 以下尝试通过各个创建工厂创建View **/
if (mFactory2 != null) view = mFactory2.onCreateView(parent,name, mContext, attrs);
else if (mFactory != null) view =mFactory.onCreateView(name, mContext, attrs);
else view = null;
if (view == null &&mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent,name, mContext, attrs);
}
if (view == null) {
/** 各个工厂都无法创建则本类自己尝试创建 **/
if (-1 == name.indexOf('.')) {
/** 判断名字中是否不含“.”(就是系统控件),通过onCreateView方法创建(具体下文解析,这里当做疑问6) **/
view = onCreateView(parent,name, attrs);
} else {
/** 判断名字中含“.”(就是自定义控件),通过createView方法创建(具体下文解析,这里当做疑问7) **/
view = createView(name,null, attrs);
}
}
return view;
} catch (InflateException e) {
throw e;
}catch (ClassNotFoundException e) {
// 异常处理略过
}
}
4.5、疑问4:parseRequestFocus(XmlPullParser parser, View parent)
这个方法解析requestFocus标签,并作用在parent上去
参数:parser:为描述接下去要解析的xml布局文件的xml解析实例
parent:将要作用的对象View
步骤:
a、调用parent的requestFocus()方法
b、requestFocus标签的子标签过滤掉
源码:
private void parseRequestFocus(XmlPullParser parser, View parent) throwsXmlPullParserException, IOException {
int type;
parent.requestFocus();
final int currentDepth =parser.getDepth();
while (((type = parser.next()) !=XmlPullParser.END_TAG ||
parser.getDepth() >currentDepth) && type != XmlPullParser.END_DOCUMENT) {
}
}
4.6、疑问5:parseInclude(XmlPullParser parser, View parent, AttributeSet attrs)
这个方法用于解析include标签,并添加到parent上去
参数:parser:为描述接下去要解析的xml布局文件的xml解析实例
parent:父控件,include标签对应的布局文件的根布局将作为parent的子控件添加上去
attrs:属性集合
步骤:
a、校验parent是否容器布局,不是则抛出InflateException异常
b、获取layout属性;校验属性,如果不是资源id,则抛出InflateException异常
c、通过资源id获取对应的xml解析实例(下文将对应的布局称为子布局)
d、校验解析实例是否合法吗,不合法抛出异常
e、解析子布局的根控件,
f、获取子布局根控件布局属性,获取不到使用include的布局属性作为子布局根控件布局属性
g、递归解析子布局
h、获取子布局根控件的id属性和visibility属性并相应设置
i、将子布局根控件添加到parent上去
j、将include标签的子标签过滤掉
源码:
private void parseInclude(XmlPullParser parser, View parent, AttributeSet attrs) throwsXmlPullParserException, IOException {
int type;
if (parent instanceof ViewGroup) {
final int layout =attrs.getAttributeResourceValue(null, "layout", 0);
if (layout == 0) {
final String value =attrs.getAttributeValue(null, "layout");
if (value == null) {
throw newInflateException("You must specifiy a layout in the" + " includetag: <include layout=\"@layout/layoutID\" />");
} else {
throw new InflateException("Youmust specifiy a valid layout " + "reference. The layout ID " +value + " is not valid.");
}
} else {
final XmlResourceParserchildParser = getContext().getResources().getLayout(layout);
try {
final AttributeSetchildAttrs = Xml.asAttributeSet(childParser);
while ((type =childParser.next()) != XmlPullParser.START_TAG && type !=XmlPullParser.END_DOCUMENT) {
}
if (type !=XmlPullParser.START_TAG) {
throw newInflateException(childParser.getPositionDescription() + ": No start tagfound!");
}
final String childName = childParser.getName();
if (TAG_MERGE.equals(childName)){
rInflate(childParser,parent, childAttrs, false);
} else {
final View view =createViewFromTag(parent, childName, childAttrs);
final ViewGroup group =(ViewGroup) parent;
ViewGroup.LayoutParamsparams = null;
try {
params =group.generateLayoutParams(attrs);
} catch(RuntimeException e) {
params =group.generateLayoutParams(childAttrs);
} finally {
if (params != null){
view.setLayoutParams(params);
}
}
rInflate(childParser,view, childAttrs, true);
TypedArray a = mContext.obtainStyledAttributes(attrs, com.android.internal.R.styleable.View,0, 0);
int id =a.getResourceId(com.android.internal.R.styleable.View_id, View.NO_ID);
int visibility =a.getInt(com.android.internal.R.styleable.View_visibility, -1);
a.recycle();
if (id != View.NO_ID) {
view.setId(id);
}
switch (visibility) {
case 0:
view.setVisibility(View.VISIBLE);
break;
case 1:
view.setVisibility(View.INVISIBLE);
break;
case 2:
view.setVisibility(View.GONE);
break;
}
group.addView(view);
}
} finally {
childParser.close();
}
}
} else {
throw newInflateException("<include /> can only be used inside of aViewGroup");
}
final int currentDepth =parser.getDepth();
while (((type = parser.next()) !=XmlPullParser.END_TAG || parser.getDepth() > currentDepth) && type!= XmlPullParser.END_DOCUMENT) {
}
}
4.7、疑问6:onCreateView(View parent, String name, AttributeSet attrs)
这个方法用于创建View
参数:parent:父控件
name:将要创建的View的名称
attrs:属性集合
返回值:对应的View
源码:
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);
}
这个方法其实我们在PhoneLayoutInflater也分析过,PhoneLayoutInflater的onCreateView方法源码:
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
// 其中sClassPrefixList = { "android.widget.","android.webkit.", "android.app." };
for (String prefix : sClassPrefixList){
try {
View view = createView(name,prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e){
}
}
return super.onCreateView(name, attrs);
}
当时我们还质疑sClassPrefixList里没有”android.view.”,其实在父类补充进去了
这里面都调用到了我们疑问7所要分析的方法
4.8、疑问7:View createView(String name, String prefix,AttributeSet attrs)
这个方法用于创建View
参数:name:将要创建的View的名称
prefix:名称前缀
attrs:属性集合
返回值:对应的View
步骤:
a、通过名称尝试在缓存中获取出对应的构造方法
b、获取不到,就通过名称和前缀拼接出View的全名,
c、通过反射机制获取出相应的类和参数为(Contex, AttributeSet)构造方法,并缓存构造方法
d、校验该类是否允许被解析,不允许就抛出异常
e、通过反射机制创建对应的View
f、如果是ViewStub则将其LayoutInflater设置为自己
源码:
public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException,InflateException {
Constructor<? extends View>constructor = sConstructorMap.get(name);
Class<? extends View> clazz =null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {
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);
sConstructorMap.put(name,constructor);
} else {
if (mFilter != null) {
Boolean allowedState =mFilterMap.get(name);
if (allowedState == null) {
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[] args = mConstructorArgs;
args[1] = attrs;
final View view =constructor.newInstance(args);
if (view instanceof ViewStub) {
final ViewStub viewStub =(ViewStub) view;
viewStub.setLayoutInflater(this);
}
return view;
} catch (NoSuchMethodException e) {
// 异常处理略过
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
其他事项
Activity也实现了Factory2接口,并且在attach的时候将其设置到mWindow的LayoutInflater的PrivateFactory进去了
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2,
// 其他代码
final void attach(Context context,ActivityThread aThread,
Instrumentation instr, IBindertoken, int ident,
Application application, Intentintent, ActivityInfo info,
CharSequence title, Activityparent, String id,
NonConfigurationInstanceslastNonConfigurationInstances,
Configuration config) {
// 其他代码
mWindow =PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
// 其他代码
其中onCreateView的实现就是解析fragment标签,其他标签继续使用父类解析策略
源码:
public View onCreateView(View parent,String name, Context context, AttributeSet attrs) {
if (!"fragment".equals(name)){
return onCreateView(name, context,attrs);
}
String fname =attrs.getAttributeValue(null, "class");
TypedArray a = context.obtainStyledAttributes(attrs,com.android.internal.R.styleable.Fragment);
if (fname == null) {
fname =a.getString(com.android.internal.R.styleable.Fragment_name);
}
int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id,View.NO_ID);
String tag =a.getString(com.android.internal.R.styleable.Fragment_tag);
a.recycle();
int containerId = parent != null ?parent.getId() : 0;
if (containerId == View.NO_ID&& id == View.NO_ID && tag == null) {
throw newIllegalArgumentException(attrs.getPositionDescription() + ": Must specifyunique android:id, android:tag, or have a parent with an id for " +fname);
}
Fragment fragment = id != View.NO_ID ?mFragments.findFragmentById(id) : null;
if (fragment == null && tag !=null) {
fragment =mFragments.findFragmentByTag(tag);
}
if (fragment == null &&containerId != View.NO_ID) {
fragment =mFragments.findFragmentById(containerId);
}
if (fragment == null) {
fragment =Fragment.instantiate(this, fname);
fragment.mFromLayout = true;
fragment.mFragmentId = id != 0 ? id: containerId;
fragment.mContainerId =containerId;
fragment.mTag = tag;
fragment.mInLayout = true;
fragment.mFragmentManager =mFragments;
fragment.onInflate(this, attrs,fragment.mSavedFragmentState);
mFragments.addFragment(fragment,true);
} else if (fragment.mInLayout) {
throw newIllegalArgumentException(attrs.getPositionDescription()
+ ": Duplicate id0x" + Integer.toHexString(id)
+ ", tag " + tag + ",or parent id 0x" + Integer.toHexString(containerId)
+ " with anotherfragment for " + fname);
} else {
fragment.mInLayout = true;
if (!fragment.mRetaining) {
fragment.onInflate(this, attrs,fragment.mSavedFragmentState);
}
mFragments.moveToState(fragment);
}
if (fragment.mView == null) {
throw newIllegalStateException("Fragment " + fname + " did not create aview.");
}
if (id != 0) {
fragment.mView.setId(id);
}
if (fragment.mView.getTag() == null) {
fragment.mView.setTag(tag);
}
return fragment.mView;
}
总结
1、LayoutInflater实例的获取
1.1、通过Activity实例的getLayoutInflater()方法方法
1.2、通过LayoutInflater.from(Context)方法获取
1.3、通过 (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)方式获取
三种获取方式最终都是调用第三种实现
2、布局特殊标签
2.1、merge标签:该标签可以将它的直接子View添加到它的父View上去,也就是减少布局层次,优化布局。注意:
该标签必须位于布局文件根结点
2.2、include标签:该标签可以引用另一个布局文件,达到布局文件复用效果。注意:
该标签不能位于布局文件的根结点
3.3、blink标签:该标签下面的所有子View(直接子View以及子View的子View等)将会出现闪烁效果。注意:闪烁周期为1秒(显示0.5秒,隐藏0.5秒)
3.4、requestFocus标签 改标签就是会为其所在的View调用一下requestFocus()方法以请求获取焦点
3、inflate方法中的ViewGroup root参数和boolean attachToRoot参数含义
root :父控件,用于
确定xml布局的根控件的LayoutParams
,设置
为空将不会解析根控件的LayoutParams
,也就是根控件中用于父控件的布局属性,包括width属性和height属性还有其他父布局需要的特殊属性,比如父布局是LinearLayout的话,而且根控件的包含有weight属性,但是root设置为null,将丢弃该属性,不为null将解析该属性,作用于根控件
attachToRoot: 是否将布局根控件添加到root控件上去,如果设置为true,但是root参数为null将抛出异常
4、include标签布局属性和子布局根控件布局属性 选择策略
优先选择include
的,获取失败再选择子布局根控件布局属性
比如include标签的width和height设置为100dp,而子布局根控件的width和height设置为200dp,则会选择使用100dp;
而如果include标签没有设置width和height属性,而子布局根控件的width和height设置为200dp,则会使用200dp。
5、view标签可以通过class属性指定具体名字