Android 使用LayoutInflater.Factory2创建布局

一,解析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);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值