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);
相關文章
相關標籤/搜索