AppCompat中的坑

http://blog.csdn.net/liuxu0703/article/details/70145168

在使用类似textView等View时, 5.0以下的版本中,android会做一个兼容性的处理,生成AppCompatTextView,这样的view的getContext()是TineContextWrapper,如果要强制转为activity或者instanceOfActivity的判断时,不会符合预期的操作

这里写图片描述

有两种解决办法

  1. 根本的解决办法,采用stackoverflow中的办法,一个工具方法来替换所有的view的getContext(),适用于工程中的这个调用的方式不多,而且修改起来风险不大的地方

  2. 当前问题的解决办法,在5.0以下,看有没有可以禁止这种行为的,是支持的,方式是: 在我们的BaseActivity的onCreate()中,调用getDegate().setCompatVectorFromResourcesEnabled()

可以通过源码查看,在AppCompatActivity的onCreate()中,是安装了一个LayoutInfatorFactory,这里就用于替换的

1) AppCompatDelegateImplV9.onCreateView

 /**
     * From {@link android.support.v4.view.LayoutInflaterFactory}
     */
    @Override
    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);
    }

2) AppCompatDelegateImplV11.callActivityOnCreateView()

 @Override
    View callActivityOnCreateView(View parent, String name, Context context, AttributeSet attrs) {
        // On Honeycomb+, Activity's private inflater factory will handle calling its
        // onCreateView(...)
        return null;
    }

发现 callActivityOnCreateView返回null,因而是createView()来控制的

3) AppCompatDelegateImplV9.createView()

 @Override
    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 */
        );
    }  

4) mAppCompatViewInflater.createView()的实现

继续翻看代码,能够看到view替换的过程,但是这里我们也看到了有控制的参数,wrapContext为true时,才会做替换,这就是VectorEnabledTintResources.shouldBeUsed()

    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 it's android:onClick
            checkOnClickListener(view, attrs);
        }

        return view;
    }

5) VectorEnabledTintResources.shouldBeUsed()

public static boolean shouldBeUsed() {
        return AppCompatDelegate.isCompatVectorFromResourcesEnabled()
                && Build.VERSION.SDK_INT <= MAX_SDK_WHERE_REQUIRED;
    }

继续追踪    AppCompatDelegate.isCompatVectorFromResourcesEnabled()

6) AppCompatDelegate.isCompatVectorFromResourcesEnabled()

在相关的方法中,可以看到,这个是可以控制的

/**
     * Sets whether vector drawables on older platforms (< API 21) can be used within
     * {@link android.graphics.drawable.DrawableContainer} resources.
     *
     * <p>When enabled, AppCompat can intercept some drawable inflation from the framework, which
     * enables implicit inflation of vector drawables within
     * {@link android.graphics.drawable.DrawableContainer} resources. You can then use those
     * drawables in places such as {@code android:src} on {@link android.widget.ImageView},
     * or {@code android:drawableLeft} on {@link android.widget.TextView}. Example usage:</p>
     *
     * <pre>
     * &lt;selector xmlns:android=&quot;...&quot;&gt;
     *     &lt;item android:state_checked=&quot;true&quot;
     *           android:drawable=&quot;@drawable/vector_checked_icon&quot; /&gt;
     *     &lt;item android:drawable=&quot;@drawable/vector_icon&quot; /&gt;
     * &lt;/selector&gt;
     *
     * &lt;TextView
     *         ...
     *         android:drawableLeft=&quot;@drawable/vector_state_list_icon&quot; /&gt;
     * </pre>
     *
     * <p>This feature defaults to disabled, since enabling it can cause issues with memory usage,
     * and problems updating {@link Configuration} instances. If you update the configuration
     * manually, then you probably do not want to enable this. You have been warned.</p>
     *
     * <p>Even with this disabled, you can still use vector resources through
     * {@link android.support.v7.widget.AppCompatImageView#setImageResource(int)} and it's
     * {@code app:srcCompat} attribute. They can also be used in anything which AppCompat inflates
     * for you, such as menu resources.</p>
     *
     * <p>Please note: this only takes effect in Activities created after this call.</p>
     */
    public static void setCompatVectorFromResourcesEnabled(boolean enabled) {
        sCompatVectorFromResourcesEnabled = enabled;
    }

    /**
     * Returns whether vector drawables on older platforms (< API 21) can be accessed from within
     * resources.
     *
     * @see #setCompatVectorFromResourcesEnabled(boolean)
     */
    public static boolean isCompatVectorFromResourcesEnabled() {
        return sCompatVectorFromResourcesEnabled;
    }

找到了设置的方法,就是在BaseActivity中super.oncreate()后,调用getDegate(). setCompatVectorFromResourcesEnabled(false), 注意这个方法的副作用,那就是在5.0以前的系统中,禁用了vector

posted @ 2017-10-19 17:43  Panda Pan  阅读(26)  评论(0编辑  收藏  举报