自android5.0推出以来,google大力宣扬Meterail Design这款视觉设计语言,在新系统上,大量的运用到了Meterail Design风格,显然这些效果低版本时并没有实现,那么google是如何在低版本中做兼容的?
在as上新建项目时我们会发现activity会自动继承AppCompatActivity,它继承至FragmentActivity,查看AppCompatActivity源码我们会发现,以前FragmentActivity中的方法都会调用AppCompatDelegate的方法。这里以onCreate方法为例
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
//调用AppCompatDelegate的onCreate方法。
delegate.onCreate(savedInstanceState);
if (delegate.applyDayNight() && mThemeId != 0) {
// If DayNight has been applied, we need to re-apply the theme for
// the changes to take effect. On API 23+, we should bypass
// setTheme(), which will no-op if the theme ID is identical to the
// current theme ID.
if (Build.VERSION.SDK_INT >= 23) {
onApplyThemeResource(getTheme(), mThemeId, false);
} else {
setTheme(mThemeId);
}
}
super.onCreate(savedInstanceState);
}
/**
* @return The {@link AppCompatDelegate} being used by this Activity.
*/
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
//调用AppCompatDelegate接口的create方法
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
AppCompatDelegate的create方法中做了sdk版本判断,分别返回了不同版本的AppCompatDelegate实现类
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
if (BuildCompat.isAtLeastN()) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (sdk >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV9(context, window, callback);
}
}
通过源码我们发现setContentView() 方法实现是在 AppCompatDelegateImplV9 这个类中。这个时候我们可以猜想下,由于Activity是通过一个xmlpull解析器根据tag,将xml解析为一个个组件和控件的(详情自行了解Activity中setContentView作用),那么,是否google在解析xml时做了一定手脚,来达到兼容的目的?我们找到setContentView(int resId)方法
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
//解析xml
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
通过inflate方法,我们最终找到createViewFromTag方法
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
/*
省略
*/
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;
/*
省略
*/
}
调用了onCreateView实例化view
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
/*
省略
*/
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
/*
省略
*/
}
Factory2是一个接口,而我们发现AppCompatDelegateImplV9 实现了LayoutInflaterFactory
class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase
implements MenuBuilder.Callback, LayoutInflaterFactory
查看LayoutInflaterFactory注释可知LayoutInflater.Factory2与LayoutInflaterFactory是一样的,所以mFactory2.onCreateView的方法实际上就是调用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);
}
调用了createView方法,通过AppCompatViewInflater返回了View对象,那么AppCompatViewInflater究竟做了什么处理?
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
final boolean isPre21 = Build.VERSION.SDK_INT < 21;
if (mAppCompatViewInflater == null) {
mAppCompatViewInflater = new AppCompatViewInflater();
}
// We only want the View to inherit its context if we're running pre-v21
final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent);
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
isPre21, /* 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 */
);
}
查看AppCompatViewInflater的createView方法,我们发现,里面做了一个偷梁换柱的处理,将我们xml中写的控件改成了AppCompat控件,来达到兼容的目的。
public final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
/*
省略
*/
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;
}
/*
省略
*/
return view;
}
总结:
AppCompatDelegate 的工作就是涂色。
替换:widget着色是通过这个widget 的layout 在inflation 的时候,被AppCompatDelegate 拦截下来,然后根据控件的名字,强制被系统转换成为 以AppCompat 开头的控件,兼容控件继承原控件,在使用过程中,开发人员写法和原来一样。