Android 使用LayoutInflater.Factory2建立布局

一,解析LayoutInflater运行原理

从建立一个LayoutInflater的方式咱们能够知道,LayoutInflater是系统提供的单例对象android

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方法最终会调用以下方法,固然这是必然的,由于咱们须要解析这个布局文件app

inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot)

关于XmlPullParser parser解析器的获取,Android内部采用了XmlpullParser解析xml,这种解析相似与SAX Parser技术,效率高,由于它以IO流的方式进行解析和读取ide

咱们来看一下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继承自XmlPullParserthis

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获取到的是XmlResourceParserspa

XmlResourceParser xml = getResources().getXml(R.xml.gateway);
XmlResourceParser layout = getResources().getLayout(R.layout.fragment_main);

这里注意,其实获取资源的时候,都会经过XmlResourceParser,只是内部进行了必要的封装而已,有兴趣的 猿猿同窗 能够查看Resource的实现代码code

 

再来看看inflate的实现xml

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);
相关文章
相关标签/搜索