AppCompactViewInflater替换旧版本Widget及对Material Design的支持

1. AppCompatViewInflater的作用

该类被AppCompat用来自动替换掉Android核心widget,在从布局文件解析的时候这些核心widget被以AppCompat结尾的控件填充。该类有两个作用:

  1. 注入染色View替换掉在布局填充中的原View的系统框架版本;
  2. 使android:theme的功能后向兼容任何的填充控件。

其使用方法如下:

  1. 假如要使用Material Design库,继承该类;
  2. 重写一个或多个createXYZ()方法;
  3. 在应用主题中添加viewInflaterClass属性,其值必须是定义的全限定类名。

MaterialComponentsViewInflater正式这样使用的,下面假设在布局文件中定义一个<Button>,在引入Material Desugn库后会被填充为一个MaterialButton。首先来看MaterialComponentsViewInflater的定义:

public class MaterialComponentsViewInflater extends AppCompatViewInflater {
  @NonNull
  @Override
  protected AppCompatButton createButton(@NonNull Context context, @NonNull AttributeSet attrs) {
    return new MaterialButton(context, attrs);
  }

  @NonNull
  @Override
  protected AppCompatCheckBox createCheckBox(Context context, AttributeSet attrs) {
    return new MaterialCheckBox(context, attrs);
  }

  @NonNull
  @Override
  protected AppCompatRadioButton createRadioButton(Context context, AttributeSet attrs) {
    return new MaterialRadioButton(context, attrs);
  }

  @NonNull
  @Override
  protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
    return new MaterialTextView(context, attrs);
  }

  @NonNull
  @Override
  protected AppCompatAutoCompleteTextView createAutoCompleteTextView(
      @NonNull Context context, @Nullable AttributeSet attrs) {
    return new MaterialAutoCompleteTextView(context, attrs);
  }
}

该类继承了AppCompatViewInflater,并重写了createXYZ的五个方法,分别替换为MaterialButton、MaterialCheckBox、MaterialRadioButton、MaterialTextView、MaterialAutoCompleteTextView。下面看该类初始化的地方,是在AppCompatDelegateImpl类中通过反射初始化的:

public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        if (mAppCompatViewInflater == null) {
            TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
            //获取viewInflaterClass属性值
            String viewInflaterClassName =
                    a.getString(R.styleable.AppCompatTheme_viewInflaterClass);
            if (viewInflaterClassName == null) {
                // Set to null (the default in all AppCompat themes). Create the base inflater
                // (no reflection)
                mAppCompatViewInflater = new AppCompatViewInflater();
            } else {
                try {
                    Class<?> viewInflaterClass = Class.forName(viewInflaterClassName);
                    mAppCompatViewInflater =
                            (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
                                    .newInstance();
                } catch (Throwable t) {
                    Log.i(TAG, "Failed to instantiate custom view inflater "
                            + viewInflaterClassName + ". Falling back to default.", t);
                    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 */
        );
    }

该段代码主要就是获取主题中定义的viewInflaterClass属性值,在Material库中有四个style定义了该属性值:

<style name="Base.V14.Theme.MaterialComponents" parent="Base.V14.Theme.MaterialComponents.Bridge">
    <item name="viewInflaterClass">com.google.android.material.theme.MaterialComponentsViewInflater</item>
</style>

<style name="Base.V14.Theme.MaterialComponents.Dialog" parent="Platform.MaterialComponents.Dialog">
    <item name="viewInflaterClass">com.google.android.material.theme.MaterialComponentsViewInflater</item>
</style>

<style name="Base.V14.Theme.MaterialComponents.Light" parent="Base.V14.Theme.MaterialComponents.Light.Bridge">
    <item name="viewInflaterClass">com.google.android.material.theme.MaterialComponentsViewInflater</item>
</style>

<style name="Base.V14.Theme.MaterialComponents.Light.Dialog" parent="Platform.MaterialComponents.Light.Dialog">
    <item name="viewInflaterClass">com.google.android.material.theme.MaterialComponentsViewInflater</item>
</style>

若应用定义了以上主题,mAppCompatViewInflater就会被初始化为MaterialComponentsViewInflater对象,不然就会被初始化为AppCompatViewInflater对象,然后调用其父类的createView()方法:

//AppCompatViewInflater.java
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
        //有的createXYZ()方法会被重写并返回对应的Material库中的控件,没有被重写的会返回带有AppCompat前缀的控件,该控件具有染色功能。
        switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Button":
                view = createButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "EditText":
                view = createEditText(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Spinner":
                view = createSpinner(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageButton":
                view = createImageButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckBox":
                view = createCheckBox(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RadioButton":
                view = createRadioButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckedTextView":
                view = createCheckedTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "AutoCompleteTextView":
                view = createAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "MultiAutoCompleteTextView":
                view = createMultiAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RatingBar":
                view = createRatingBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "SeekBar":
                view = createSeekBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ToggleButton":
                view = createToggleButton(context, attrs);
                verifyNotNull(view, name);
                break;
            default:
                // The fallback that allows extending class to take over view inflation
                // for other tags. Note that we don't check that the result is not-null.
                // That allows the custom inflater path to fall back on the default one
                // later in this method.
                view = createView(context, name, attrs);
        }

        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;
    }

除了MaterialComponentsViewInflater中重载的五个createXYZ()方法外,其余的create方法依然会执行其父类AppCompatViewInflater中的方法,没有被重写的会返回带有AppCompat前缀的控件,该控件具有染色功能。

2. AppCompat Widget功能总结

AppCompat Widget主要实现了各个控件的染色功能,Material Design库中相应控件均继承了AppCompat Widget。

2.1. TextView

2.1.1 AppCompatTextView

  1. 背景染色
 * {@link androidx.appcompat.R.attr#backgroundTint} and
 * {@link androidx.appcompat.R.attr#backgroundTintMode}.</li>
  1. 自动化字体大小:
 * {@link androidx.appcompat.R.attr#autoSizeTextType},
 * {@link androidx.appcompat.R.attr#autoSizeMinTextSize},
 * {@link androidx.appcompat.R.attr#autoSizeMaxTextSize},
 * {@link androidx.appcompat.R.attr#autoSizeStepGranularity} and
 * {@link androidx.appcompat.R.attr#autoSizePresetSizes},

2.1.2 MaterialTextView

  1. 行间距
    通过TextAppearance样式设置android:lineHeight,参考MaterialTextView

2.2. AppCompatImageView

  1. 背景染色
 *{@link androidx.appcompat.R.attr#backgroundTint} and
 *{@link androidx.appcompat.R.attr#backgroundTintMode}.</li>
  1. 图片染色
 * {@link androidx.appcompat.R.attr#tint} and
 * {@link androidx.appcompat.R.attr#tintMode}.</li>

2.3. Button

2.3.1 AppCompatButton

  1. 背景染色
 *{@link R.attr#backgroundTint} and
 *{@link R.attr#backgroundTintMode}.</li>

2.3.2 MaterialButton

  1. 不要设置{@code android:background}属性,它会设置自己的背景Drawable;
  2. 对于填充型,MaterialButton会自动用主题的{@code ?attr/colorPrimary}做为背景染色,{@code ?attr/colorOnPrimary}做为字体颜色;非填充型只设置字体颜色
  3. 设置图片及图片方位:
* <p>Add icons to the start, center, or end of this button using the {@link R.attr#icon app:icon},
* {@link R.attr#iconPadding app:iconPadding}, {@link R.attr#iconTint app:iconTint}, {@link
* R.attr#iconTintMode app:iconTintMode} and {@link R.attr#iconGravity app:iconGravity} attributes.
  1. 首对齐图标
  2. 背景染色
  3. 设置水波纹颜色
  4. 边框颜色及宽度

具体使用参考MaterialButton

2.4. AppCompatEditText

  1. 背景染色
*{@link R.attr#backgroundTint} and
*{@link R.attr#backgroundTintMode}.</li>

2.5. AppCompatSpinner

1.背景染色

2.6. AppCompatImageButton

  1. 背景染色
  2. 图片染色

2.7. CheckBox

2.7.1. AppCompatCheckBox

1.背景染色

2.7.2. MaterialCheckBox

参考MaterialCheckBox

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值