美文网首页
LayoutInflaterCompat.setFactory2

LayoutInflaterCompat.setFactory2

作者: 有点健忘 | 来源:发表于2018-06-05 15:55 被阅读314次

    全局修改一些view的属性的简单办法,就是标题的方法
    需要在super.onCreate(savedInstanceState)之前添加,因为一个activity只能设置一个,而系统在super方法里也调用了这个方法。。所以我们需要在系统设置之前。

        override fun onCreate(savedInstanceState: Bundle?) {
            LayoutInflaterCompat.setFactory2(layoutInflater,object :LayoutInflater.Factory2{
                override fun onCreateView(parent: View?, name: String?, context: Context, attrs: AttributeSet): View? {
                    var view=delegate.createView(parent,name,context,attrs);//调用系统的方法
                    println("onCreateView56=====$name===========${view}")
                    LinearLayout(context,attrs)
                    return  view
                }
    
                override fun onCreateView(name: String?, context: Context?, attrs: AttributeSet?): View? {
                    return  null
                }
            })
            super.onCreate(savedInstanceState)
        }
    

    获取id的办法,打印下获取到的id就是 @+十进制id,比如这样id===============@2131296538

                    (0 until attrs.attributeCount).forEach {
                        println("${name}=====$it==========${attrs.getAttributeName(it)}")
                        if(TextUtils.equals("id",attrs.getAttributeName(it))){
                            try {view.id
                                println("id===============${attrs.getAttributeValue(it)}")
                                if(TextUtils.equals(attrs.getAttributeValue(it),"@${android.R.id.message}")){
                                    if(view!=null&& view is TextView){
                                        view.setTextColor(Color.RED)
                                    }
                                }
                            }catch (e:Exception){
                                e.printStackTrace()
                            }
                        }
                    }
    

    然后先说下上边获取的view方法,最终是在这里AppCompatViewInflater类里
    可以看到除了基本控件基本返回的view都是null

    public final View createView(View parent, final String name, @NonNull Context context,
                @NonNull AttributeSet attrs, boolean inheritContext,
                boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
            final Context originalContext = context;
    
            // We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
            // by using the parent's context
            if (inheritContext && parent != null) {
                context = parent.getContext();
            }
            if (readAndroidTheme || readAppTheme) {
                // We then apply the theme on the context, if specified
                context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
            }
            if (wrapContext) {
                context = TintContextWrapper.wrap(context);
            }
    
            View view = null;
    
            // We need to 'inject' our tint aware Views in place of the standard framework versions
            switch (name) {
                case "TextView":
                    view = new AppCompatTextView(context, attrs);
                    break;
                case "ImageView":
                    view = new AppCompatImageView(context, attrs);
                    break;
                case "Button":
                    view = new AppCompatButton(context, attrs);
                    break;
                case "EditText":
                    view = new AppCompatEditText(context, attrs);
                    break;
                case "Spinner":
                    view = new AppCompatSpinner(context, attrs);
                    break;
                case "ImageButton":
                    view = new AppCompatImageButton(context, attrs);
                    break;
                case "CheckBox":
                    view = new AppCompatCheckBox(context, attrs);
                    break;
                case "RadioButton":
                    view = new AppCompatRadioButton(context, attrs);
                    break;
                case "CheckedTextView":
                    view = new AppCompatCheckedTextView(context, attrs);
                    break;
                case "AutoCompleteTextView":
                    view = new AppCompatAutoCompleteTextView(context, attrs);
                    break;
                case "MultiAutoCompleteTextView":
                    view = new AppCompatMultiAutoCompleteTextView(context, attrs);
                    break;
                case "RatingBar":
                    view = new AppCompatRatingBar(context, attrs);
                    break;
                case "SeekBar":
                    view = new AppCompatSeekBar(context, attrs);
                    break;
            }
    
            if (view == null && originalContext != context) {
                // If the original context does not equal our themed context, then we need to manually
                // inflate it using the name so that android:theme takes effect.
                view = createViewFromTag(context, name, attrs);
            }
    
            if (view != null) {
                // If we have created a view, check its android:onClick
                checkOnClickListener(view, attrs);
            }
    
            return view;
        }
    

    我们看下系统的用法,在AppCompatActivity类里

    protected void onCreate(@Nullable Bundle savedInstanceState) {
            final AppCompatDelegate delegate = getDelegate();
            delegate.installViewFactory();
            delegate.onCreate(savedInstanceState);
    
    
    //
        @NonNull
        public AppCompatDelegate getDelegate() {
            if (mDelegate == null) {
                mDelegate = AppCompatDelegate.create(this, this);
            }
            return mDelegate;
        }
    
    // public abstract class AppCompatDelegate
    
        public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
            return create(activity, activity.getWindow(), callback);
        }
    
    //
        private static AppCompatDelegate create(Context context, Window window,
                AppCompatCallback callback) {
            if (Build.VERSION.SDK_INT >= 24) {
                return new AppCompatDelegateImplN(context, window, callback);
            } else if (Build.VERSION.SDK_INT >= 23) {
                return new AppCompatDelegateImplV23(context, window, callback);
            } else if (Build.VERSION.SDK_INT >= 14) {
                return new AppCompatDelegateImplV14(context, window, callback);
            } else if (Build.VERSION.SDK_INT >= 11) {
                return new AppCompatDelegateImplV11(context, window, callback);
            } else {
                return new AppCompatDelegateImplV9(context, window, callback);
            }
        }
    
    

    在AppCompatDelegateImplV9里实现了最开始的installViewFactory()

    另外在14里边实现了白天黑夜模式

    class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
            implements MenuBuilder.Callback, LayoutInflater.Factory2
    
    //可以看到只能设置一个factory
    
        public void installViewFactory() {
            LayoutInflater layoutInflater = LayoutInflater.from(mContext);
            if (layoutInflater.getFactory() == null) {
                LayoutInflaterCompat.setFactory2(layoutInflater, this);
            } else {
                if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
                    Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                            + " so we can not install AppCompat's");
                }
            }
        }
    

    继续往下LayoutInflaterCompat

        @RequiresApi(21)
        static class LayoutInflaterCompatApi21Impl extends LayoutInflaterCompatBaseImpl {
            @SuppressWarnings("deprecation")
            @Override
            public void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
                inflater.setFactory2(factory != null ? new Factory2Wrapper(factory) : null);
            }
    
            @Override
            public void setFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
                inflater.setFactory2(factory);
            }
        }
        static final LayoutInflaterCompatBaseImpl IMPL;
        static {
            if (Build.VERSION.SDK_INT >= 21) {
                IMPL = new LayoutInflaterCompatApi21Impl();
            } else {
                IMPL = new LayoutInflaterCompatBaseImpl();
            }
        }
    
        public static void setFactory2(
                @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
            IMPL.setFactory2(inflater, factory);
        }
    
    
    //看下impl
        static class LayoutInflaterCompatBaseImpl {
            @SuppressWarnings("deprecation")
            public void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
                final LayoutInflater.Factory2 factory2 = factory != null
                        ? new Factory2Wrapper(factory) : null;
                setFactory2(inflater, factory2);
            }
    
            public void setFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
                inflater.setFactory2(factory);
    
                final LayoutInflater.Factory f = inflater.getFactory();
                if (f instanceof LayoutInflater.Factory2) {
                    // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
                    // We will now try and force set the merged factory to mFactory2
                    forceSetFactory2(inflater, (LayoutInflater.Factory2) f);//通过反射把factory赋值给LayoutInflater。
                } else {
                    // Else, we will force set the original wrapped Factory2
                    forceSetFactory2(inflater, factory);
                }
            }
    
            @SuppressWarnings("deprecation")
            public LayoutInflaterFactory getFactory(LayoutInflater inflater) {
                LayoutInflater.Factory factory = inflater.getFactory();
                if (factory instanceof Factory2Wrapper) {
                    return ((Factory2Wrapper) factory).mDelegateFactory;
                }
                return null;
            }
        }
    

    我们知道我们的布局文件加载就是靠LayoutInflater的,现在看下它的加载

        public View inflate(@LayoutRes int resource, @Nullable 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();
            }
        }
    
    //继续看下inflate方法,删掉一些代码,看下核心代码就行
        public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
            synchronized (mConstructorArgs) {
                Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
    
                final Context inflaterContext = mContext;
                final AttributeSet attrs = Xml.asAttributeSet(parser);
                Context lastContext = (Context) mConstructorArgs[0];
                mConstructorArgs[0] = inflaterContext;
                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();
                  //处理merge标签的
                    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, inflaterContext, attrs, false);
                    } else {
                        // Temp is the root view that was found in the xml 处理普通布局的
                        final View temp = createViewFromTag(root, name, inflaterContext, attrs);
    
                        ViewGroup.LayoutParams params = null;
    
                        if (root != null) {
                  
                            // 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);
                            }
                        }
    
                        // Inflate all children under temp against its context.
                        rInflateChildren(parser, temp, attrs, true);
    
                        // 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) {
                } catch (Exception e) {
                } finally {
                }
    
                return result;
            }
        }
    
    

    下边分别说明下上边几个方法

    对于merge布局走了这里,就是循环读取标签解析出对应的控件。

      /**
         * Recursive method used to descend down the xml hierarchy and instantiate
         * views, instantiate their children, and then call onFinishInflate().
         * <p>
         * <strong>Note:</strong> Default visibility so the BridgeInflater can
         * override it.
         */
        void rInflate(XmlPullParser parser, View parent, Context context,
                AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    
            final int depth = parser.getDepth();
            int type;
            boolean pendingRequestFocus = false;
    
            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)) {
                    pendingRequestFocus = true;
                    consumeChildElements(parser);
                } 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, context, parent, attrs);
                } else if (TAG_MERGE.equals(name)) {
                    throw new InflateException("<merge /> must be the root element");
                } else {
                    final View view = createViewFromTag(parent, name, context, attrs); //view的最后获取都是在这里的
                    final ViewGroup viewGroup = (ViewGroup) parent;
                    final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                    rInflateChildren(parser, view, attrs, true);
                    viewGroup.addView(view, params);
                }
            }
    
            if (pendingRequestFocus) {
                parent.restoreDefaultFocus();
            }
    
            if (finishInflate) {
                parent.onFinishInflate();
            }
        }
    

    createViewFromTag

    结论是先调用我们设置的factory的方法获取view,如果为空再调用系统原生的。

        private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
            return createViewFromTag(parent, name, context, attrs, false);
        }
    
    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                boolean ignoreThemeAttr) {
            if (name.equals("view")) {
                name = attrs.getAttributeValue(null, "class");
            }
    
            // Apply a theme wrapper, if allowed and one is specified.
            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)) {
                // Let's party like it's 1995!
                return new BlinkLayout(context, attrs);
            }
    
            try {
                View view;//可以看到先调用我们设置的factory的方法获取view
                if (mFactory2 != null) {
                    view = mFactory2.onCreateView(parent, name, context, attrs);
                } else if (mFactory != null) {
                    view = mFactory.onCreateView(name, context, attrs);
                } else {
                    view = null;
                }
              //mPrivateFactory 也是个factory
                if (view == null && mPrivateFactory != null) {
                    view = mPrivateFactory.onCreateView(parent, name, context, attrs);
                }
    
                if (view == null) {//如果我们factory的回调方法里返回的view是空的,那么这里处理
                    final Object lastContext = mConstructorArgs[0];
                    mConstructorArgs[0] = context;
                    try {
                        if (-1 == name.indexOf('.')) {//这里2个就是系统创建view的过程了,不研究
                            view = onCreateView(parent, name, attrs);
                        } else {
                            view = createView(name, null, attrs);
                        }
                    } finally {
                        mConstructorArgs[0] = lastContext;
                    }
                }
    
                return view;
            } catch (InflateException e) {
            } catch (ClassNotFoundException e) {
            } catch (Exception e) {
            }
        }
    

    在AppCompatDelegateImplV9 createView方法

    可以看到调用了AppCompatViewInflater里的createView方法也就是对原始的控件,都改成了AppcompatXXXX的东西。

        public View createView(View parent, final String name, @NonNull Context context,
                @NonNull AttributeSet attrs) {
            if (mAppCompatViewInflater == null) {
                mAppCompatViewInflater = new AppCompatViewInflater();
            }
    
            boolean inheritContext = false;
            if (IS_PRE_LOLLIPOP) {
                inheritContext = (attrs instanceof XmlPullParser)
                        // If we have a XmlPullParser, we can detect where we are in the layout
                        ? ((XmlPullParser) attrs).getDepth() > 1
                        // Otherwise we have to use the old heuristic
                        : shouldInheritContext((ViewParent) parent);
            }
    
            return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                    IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
                    true, /* Read read app:theme as a fallback at all times for legacy reasons */
                    VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
            );
        }
    

    最后稍微总结下:
    AppCompatDelegate从v9开始就实现了Factory2,也就是它也是个factory。然后它通过反射赋值给了Layouinflator。 Layouinflator加载布局的过程我们也分析了,里边就有用到factory,先通过factory获取view【就是在这里完成了对原始view的修改】,获取 为空的话再用最原始的方法生成view。
    看下v9下, mFactory2.onCreateView() 其实最终还是掉的createView方法,这个方法又跑到AppCompatViewInflater里的createView方法里的,这是最终修改view的地方。

        public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
            // First let the Activity's Factory try and inflate the view
            final View view = callActivityOnCreateView(parent, name, context, attrs);
            if (view != null) {
                return view;
            }
    
            // If the Factory didn't handle it, let our createView() method try
            return createView(parent, name, context, attrs);
        }
    

    相关文章

      网友评论

          本文标题:LayoutInflaterCompat.setFactory2

          本文链接:https://www.haomeiwen.com/subject/wmipsftx.html