一,解析LayoutInflater运行原理
从创建一个LayoutInflater的方式我们可以知道,LayoutInflater是系统提供的单例对象
LayoutInflater layoutInflater = getLayoutInflater();
↓
LayoutInflater layoutInflater = LayoutInflater.from(context);
↓
LayoutInflater LayoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
boolean equals = getLayoutInflater().equals(LayoutInflater.from(this));
Log.e("equals", "value="+equals);
#输出value=true
#说明LayoutInflater具有全局属性
关于Inflate方法,主要分为2组,但前2组最终也是通过调用后2组中的某一个方法来实现的
inflate(int resource, ViewGroup root)
inflate(int resource, ViewGroup root, boolean attachToRoot)
-------------------------------------------------------------------------
inflate(XmlPullParser parser, ViewGroup root)
inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
inflate方法最终会调用如下方法,当然这是必然的,因为我们需要解析这个布局文件
inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)
关于XmlPullParser parser解析器的获取,Android内部采用了XmlpullParser解析xml,这种解析类似与SAX Parser技术,效率高,因为它以IO流的方式进行解析和读取
我们来看一下XmlPullParser获取的这个实现方式
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
if (DEBUG) System.out.println("INFLATING from resource: " + resource);
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
在Android内部XmlResourceParser继承自XmlPullParser
public interface XmlResourceParser extends XmlPullParser, AttributeSet {
/**
* Close this interface to the resource. Calls on the interface are no
* longer value after this call.
*/
public void close();
}
实际我们通过Resource获取到的是XmlResourceParser
XmlResourceParser xml = getResources().getXml(R.xml.gateway);
XmlResourceParser layout = getResources().getLayout(R.layout.fragment_main);
这里注意,其实获取资源的时候,都会通过XmlResourceParser,只是内部进行了必要的封装而已,有兴趣的 猿猿同学 可以查看Resource的实现代码
再来看看inflate的实现
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context)mConstructorArgs[0];
mConstructorArgs[0] = mContext;
View result = root;
try {
int type;
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();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
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");
}
rInflate(parser, root, attrs, false);
} else {
// Temp is the root view that was found in the xml
View temp;
if (TAG_1995.equals(name)) {
temp = new BlinkLayout(mContext, attrs);
} else {
temp = createViewFromTag(root, name, attrs);
}
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);
if (!attachToRoot) {
// 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
rInflate(parser, temp, attrs, true);
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.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) {
InflateException ex = new InflateException(e.getMessage());
ex.initCause(e);
throw ex;
} catch (IOException e) {
InflateException ex = new InflateException(
parser.getPositionDescription()
+ ": " + e.getMessage());
ex.initCause(e);
throw ex;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
}
return result;
}
}
基本上就是解析xml的过程,xml解析本来过慢,因此在开发中应该减少使用inflate,采用ViewHolder和SparseArray缓存View是一个不错的选择
对于属性的读取,我们看到
XmlResourceParser parser = getResources().getLayout(resid);
final AttributeSet attrs = Xml.asAttributeSet(parser);
我们看到,使用了android.util.Xml类
public static AttributeSet asAttributeSet(XmlPullParser parser) {
return (parser instanceof AttributeSet)
? (AttributeSet) parser
: new XmlPullAttributes(parser);
}
AttributeSet 会最终被返回给View的Context
二.使用LayoutInflater.Factory2控件工厂
Android自定义控件的思想,获取自定义属性一般会在构造方法中,通过TypeArray和obtainStyledAttributes(resid, attrs)方法,但obtainStyledAttributes(resid, attrs)是没有公开的方法,对于这一点要特别之处, 在外部无法获得自定属性,除非重新使用XmlPullParser解析,从而得到相应的数据值 。
特别是对于Android MVVM开发而言,如何访问绑定字段,对于这一问而言,我们需要构建自己的LayoutInflater或者通过XmlPullParser解析之后与对应的view自动匹配,不过后者简单,但效率会有所损失。
综上,obtainStyledAttributes(resid, attrs)具有局限性,我们可以利用LayoutInflater.Factory2来实现属性的获取和View的重建。
先看看LayoutInflater类中的createViewFromTag源码:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
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();
}
if (name.equals(TAG_1995)) {
return new BlinkLayout(context, attrs); //内部Layout,不用理会
}
try {
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs); //在这里调用了Factory2的接口
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);//在这里调用了Factory的接口,Factory2也继承了Factory
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs); //私有实现的Factory2
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
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);
ie.initCause(e);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name);
ie.initCause(e);
throw ie;
}
}
我们实现Factory2工厂即可,返回值可以为null,也可以自行实现创建View,我们这里选择实现创建View
public class ViewCreateFactory implements Factory2 ,LayoutInflater.Filter{
private static ViewCreateFactory instance;
private Context mContext;
private OnInflaterListener onInflaterlistener;
private LayoutInflater mInflater;
private LayoutInflater.Filter mFilter;
private final static String DEFAULT_VIEW_PREFIX = "android.view.";
private final static String DEFAULT_WIDGET_PREFIX = "android.widget.";
public void setOnInflaterListener(OnInflaterListener listener) {
this.onInflaterlistener = listener;
}
public static ViewCreateFactory create(Context ctx,LayoutInflater inflater) //在onCreate中调用
{
if(instance ==null)
{
synchronized (ViewCreateFactory.class)
{
if(instance ==null)
{
instance = new ViewCreateFactory(ctx);
}
}
}
instance.setOnInflaterListener(null);
instance.setFilter(null);
instance.setLayoutInflater(inflater);
return instance;
}
public void setLayoutInflater(LayoutInflater inflater) {
this.mInflater = inflater;
this.mInflater.setFactory2(this); //建工厂设置到LayoutInflater中
this.mFilter = this.mInflater.getFilter();
this.mInflater.setFilter(this);
}
private ViewCreateFactory(Context context)
{
this.mContext = context;
}
public void setFilter(LayoutInflater.Filter filter) {
this.mFilter = filter;
}
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return onCreateView(name, context, attrs);
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
try{
View view = null;
if (-1 == name.indexOf('.'))
{
Class<?> clazz = ReflectUtils.loadClass(mContext, DEFAULT_VIEW_PREFIX.concat(name));
if(clazz!=null)
{
view = mInflater.createView(name, DEFAULT_VIEW_PREFIX, attrs); //这里我们调用LayoutInflater创建View的方法,当然也可以自定义
}
else
{
view = mInflater.createView(name,DEFAULT_WIDGET_PREFIX, attrs);
}
} else {
view = mInflater.createView(name, null, attrs);
}
if(onInflaterlistener !=null)
{
onInflaterlistener.onCreateView(view,attrs);
}
return view;
}
catch (Exception e)
{
Log.e("InflaterERROR",e.getLocalizedMessage());
e.printStackTrace();
}
return null;
}
@Override
public boolean onLoadClass(Class clazz)
{
onInflaterlistener.onLoadClass(clazz);
if(this.mFilter!=null)
{
return mFilter.onLoadClass(clazz);
}
return true;
}
//在onDestory中调用,因为LayoutInflater是全局的,因此,为了让Activity回收正常,结束时必须调用此方法
public void release()
{
this.mInflater.setFactory2(null);
this.mInflater.setFilter(null);
this.mInflater = null;
}
public interface OnInflaterListener
{
//用于向外部提供属性和View,我们可以从类外部获取到属性了
public void onCreateView(view,attrs);
}
}
//这里我们调用LayoutInflater创建View的方法,当然也可以自定义实现自己创建方法
view = mInflater.createView(name, prefix, attrs);
public interface OnInflaterListener
{
//用于向外部提供属性和View,我们可以从类外部获取到属性了
public void onCreateView(View view,AttributeSet attrs);
}
使用方式
LayoutInflater inflater = LayoutInflater.from(context);
ViewCreateFactory factory = ViewCreateFactory.create(context,inflater);
factory.setOnInflaterListener(new ViewCreateFactory.OnInflaterListener{
public void onCreateView(View view,AttributeSet attrs)
{
//从这里获取attrs,里面包含自定义属性
}
});
三.LayoutInflater可替代方式
TextView myView = (TextView)View.inflate(context, resource, root);