1、概述
LayoutInflater setFactory是什么,干什么用的,这里不多说,这里推荐鸿洋大神的一篇以及上一篇换肤技术博客,相信解读完之后你就知道它有什么魔力,以及能熟练运用Factory。
用法:http://blog.csdn.net/lmj623565791/article/details/51503977
换肤技术:http://blog.csdn.net/zhongwn/article/details/52891902
LayoutInflater setFactory确实是个好东西,v7包新组建的新特性向下兼容都是通过这玩意来做到的,也很佩服谷歌这帮工程师的设计,因为这个Factory就像是加载view的后门,我相信他们也是想到了后续可能用到而设计的框架。好了不多说了,接下来我们就一探究竟,看看它有什么魅力与魔力。
2、源码解读
既然我们知道Factory是在LayoutInflater中,那么我们就从LayoutInflater开始解读。
作为android猿,我们都知道LayoutInflater是用来加载xml布局的,一般来说我们都是通过以下两种来调用
(1)
LayoutInflater.from(this).inflate(R.Layout.xml,null);
(2)
View.inflate(this, android.R.layout.activity_list_item,null);而第二种我们跟进去看看
public static View inflate(Context context, int resource, ViewGroup root) { LayoutInflater factory = LayoutInflater.from(context); return factory.inflate(resource, root); }发现也是调用第一种方式来加载布局。那么我们就来分析分析第一种方式,而且也是顺着加载布局开始一点点来分析。接下来看看from()这个静态方法
/** * Obtains the LayoutInflater from the given context. */ 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; }这里只是通过context从系统服务中获取LayoutInflater一个实例,所以我们重点放在inflate()方法中,进入LayoutInfater中你会发现里边有4个inflate方法,其中有2个分别调用2个,而最后都统一调用一个方法来解析,这里贴着两个方法:
<1>
public View inflate(int resource, ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); if (DEBUG) { Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" (" + Integer.toHexString(resource) + ")"); } final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }这个方法很短,一看就知道它只是简单的获取Resource资源,然后通过我们传进来的 R.layout.id获取一个xml解析器,最后还是调用到第二个方法中;其中有两个方法值得关注与思考如下:
getContext().getResources();
res.getLayout(resource);它是怎么获取Resources实例以及getLayout是怎么创建这个parser解析器实例的,这里我们暂时不做分析,留给读者自行分析。这里我们只要知道它获取的是R.layout.xml布局的解析器,接下来我们有理由相信所有的开始源于第二个方法咯。瞅瞅看便知咯
<2>
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate"); final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; 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 (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, false); } else { // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, attrs, false); 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, 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; } Trace.traceEnd(Trace.TRACE_TAG_VIEW); return result; } }这个方法有点长,我们挑重点看:
final AttributeSet attrs = Xml.asAttributeSet(parser);这里通过解析器获取整个xml属性集合attrs即将xml存储在attrs,它相当于xml布局的文档树。
简单说明西夏attrs的由来,由于parser并没有实现AttributeSet接口,所以attrs最终是由XmlPullAttribute组合parser实现的。继续往下走。
// 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!"); }
这里是一个空循环,主要目的是获取xml布局最外层的View,也就是xml的root。root也是我们要解析布局的开始标签START_TAG,若是这个布局不是这个跟标签开始的,则认为布局有问题,直接抛异常。按正常流程走,
final String name = parser.getName(); 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, false); } else { // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, attrs, false);
首先获取节点名(第一次时是根节点,其它都是子节点),假设不是是以merge包含一个布局的,那么就会走else流程,接下来会执行=一个比较重要的方法:createViewFromTag():
第一个if判断当前节点 (第一次时是根节点,其它都是子节点)是否是TAG_1995,View createViewFromTag(View parent, String name, AttributeSet attrs, boolean inheritContext) { //省略多余代码………… if (name.equals(TAG_1995)) { // Let's party like it's 1995! return new BlinkLayout(viewContext, attrs); } try { View view; if (mFactory2 != null) { view = mFactory2.onCreateView(parent, name, viewContext, attrs); } else if (mFactory != null) { view = mFactory.onCreateView(name, viewContext, attrs); } else { view = null; } if (view == null && mPrivateFactory != null) { view = mPrivateFactory.onCreateView(parent, name, viewContext, attrs); } if (view == null) { //省略多余代码………… if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } //省略多余代码………… } return view;
TAG_1995 = "blink"这个当然不是,我们的组件中从没见过blink这个节点也没用过,只知道是继承自FrameLayout喜欢探究的自行研究,所以我们已正常的思路走下去。往下看,亮点来了……
呐呐呐~~一系列的Factory,每个Factory的功能基本都是一样的,看看他们的定义
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); }功能基本一样,都是同一个Factory。
当我们没赋值给任何一个Factory时,所有都是空的,所以我们view是空的
//省略多余代码………… if (-1 == name.indexOf('.')) { view = onCreateView(parent, name, attrs); } else { view = createView(name, null, attrs); } //省略多余代码…………
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { return createView(name, "android.view.", attrs); }
这里首先会判断当前是否是系统组件,如果是则执行oncreateView,如果不是则执行createView();这两个都是通过反射获取获取view的实例返回;唯一不同的是一个是自定义view一个系统组件,最后onCreateView也是通过调用createView方法来实现,只是将组件包名传过去。。最终都会获取一个view返回。
当Factory为null的时候,这里的节点view(这里是第一个根节点,后续会是一个递归获取子节点)获取就结束了
当Factory不为null的时候,这会执行Factory的回调onCreateView(由开发者自行实现,如换肤功能就是通过这个实现的),若其中Factory返回的View不为null,则这个view就是返回给window粘贴显示在界面上的视图。若为null,则执行上述分析的后续流程。
所以Factory算是一个钩子,专门拦截xml的view节点,而且是每解析xml的一个节点就会执行一次,所以通过它可以实现换肤以及替换view 如将xml的textview替换成imageview,达到偷天换日的效果。
看看赋值的方法分别对应3个,其中有一个已经hide表示只用于系统,不对开发者开放,没关系,因为剩下的两个优先级高于hide方法中privateFactory,从代码可以看出来:三个方法如下:
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; if (mFactory == null) { mFactory = factory; } else { mFactory = new FactoryMerger(factory, null, mFactory, mFactory2); } } /** * Like {@link #setFactory}, but allows you to set a {@link Factory2} * interface. */ 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; if (mFactory == null) { mFactory = mFactory2 = factory; } else { mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2); } } /** * @hide for use by framework */ public void setPrivateFactory(Factory2 factory) { if (mPrivateFactory == null) { mPrivateFactory = factory; } else { mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory); } }
你会发现setPrivateFactory这得隐藏了,再看看其它两个基本都是一样的,这里可以解读到的信息如下:
1、一个InflateLayout只能调用一次setFactory()或setFactory2()方法,否则会直接抛异常
2、若是通过InflateLayout的构造方法获取实例并将Factory的一个实例传进来,那么当调用setFactory时,将会用FactoryMerge将这两个Factory实例保留,详见类的实现。比较简单看看代码应该很容易懂
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; } 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); } 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); } }
接下来看看是怎么递归循环获取它的所有子节点的:思路回到最初的地方,即使inflate()方法中:
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, false); } else { // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, attrs, false); 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, true);
细心的你会发现不管是if还是else都有会调用如下方法:
// Inflate all children under temp rInflate(parser, temp, attrs, true, true);
其中玄妙,个中原理以及查找所有子节点,都在这个方法里。迫不及待的进去一瞧:
void rInflate(XmlPullParser parser, View parent, final AttributeSet attrs, boolean finishInflate, boolean inheritContext) 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)) { parseRequestFocus(parser, parent); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, parent, attrs, inheritContext); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } else { final View view = createViewFromTag(parent, name, attrs, inheritContext); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflate(parser, view, attrs, true, true); viewGroup.addView(view, params); } } if (finishInflate) parent.onFinishInflate(); }
分两步走:
一:这里假设这个布局没有使用merge和include
一:这里假设这个布局没有使用merge和include
在这里也开始通过while循环开始读取xml的所有节点,留心的你,这次while循环体里边会执行所有view的操作,那么就会执行到最后一个else,你会发现
相继调用createViewFromTag()创建view,这个过程就是上述Factory,为什么每执行一个节点就会被回调一次的原因。然后继续执行rInflate()方法,由于解析器还是原来的那一个,所以可以递归查找下一个子节点。
二:如果包含了merge或include来引用布局,则
会执行如下两个方法中的一个
else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, parent, attrs, inheritContext);
这两个方法:最终也还是会调用rflate()方法进行递归查找,这里就不一一分析了。
到此InflaterLayout也就解读结束了,也应该清楚Factory的面貌,至于怎么用就由开发者自行定义咯。
若有不对的地方,请不吝指正!!!
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, false); } else { // Temp is the root view that was found in the xml final View temp = createViewFromTag(root, name, attrs, false); 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, true);