1. AppCompatViewInflater的作用
该类被AppCompat用来自动替换掉Android核心widget,在从布局文件解析的时候这些核心widget被以AppCompat结尾的控件填充。该类有两个作用:
- 注入染色View替换掉在布局填充中的原View的系统框架版本;
- 使android:theme的功能后向兼容任何的填充控件。
其使用方法如下:
- 假如要使用Material Design库,继承该类;
- 重写一个或多个createXYZ()方法;
- 在应用主题中添加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
- 背景染色
* {@link androidx.appcompat.R.attr#backgroundTint} and
* {@link androidx.appcompat.R.attr#backgroundTintMode}.</li>
- 自动化字体大小:
* {@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
- 行间距
通过TextAppearance样式设置android:lineHeight,参考MaterialTextView
2.2. AppCompatImageView
- 背景染色
*{@link androidx.appcompat.R.attr#backgroundTint} and
*{@link androidx.appcompat.R.attr#backgroundTintMode}.</li>
- 图片染色
* {@link androidx.appcompat.R.attr#tint} and
* {@link androidx.appcompat.R.attr#tintMode}.</li>
2.3. Button
2.3.1 AppCompatButton
- 背景染色
*{@link R.attr#backgroundTint} and
*{@link R.attr#backgroundTintMode}.</li>
2.3.2 MaterialButton
- 不要设置{@code android:background}属性,它会设置自己的背景Drawable;
- 对于填充型,MaterialButton会自动用主题的{@code ?attr/colorPrimary}做为背景染色,{@code ?attr/colorOnPrimary}做为字体颜色;非填充型只设置字体颜色
- 设置图片及图片方位:
* <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.
- 首对齐图标
- 背景染色
- 设置水波纹颜色
- 边框颜色及宽度
具体使用参考MaterialButton
2.4. AppCompatEditText
- 背景染色
*{@link R.attr#backgroundTint} and
*{@link R.attr#backgroundTintMode}.</li>
2.5. AppCompatSpinner
1.背景染色
2.6. AppCompatImageButton
- 背景染色
- 图片染色
2.7. CheckBox
2.7.1. AppCompatCheckBox
1.背景染色