最近在整理项目将,将项目的各个Module的依赖统一,同时将之前的Android surpport兼容库,换成AndroidX 其中Android Studio的Refator–>Migrate to AndroidX并不能解决所有所有问题,由于包名的匹配失败的原因,导致,很大部分还是需要自己手工做迁移,但是这个还不是最大坑,由于Android打包工具会把我们声明在layout xml 的CompoundButton的一级子类例如CheckBox或者RadioButton替换成AppCompatCheckBox或者AppCompatRadioButton,有时候自己写的并非是真实的。

提交的bug地址 https://issuetracker.google.com/issues/151294239

正当兴高采烈的将完成了,一堆需要replace的地方replace掉的时候,App现在至少能跑起来了,但是你会看到咋有些地方UI显示的不对,例如下图,明明我在xml里有声明 android:button=”@null” 的,咋有个复选框呀!

先把几个好改的改成LinerLayout里面塞几个TextView,然而在其他地方还充斥着类似的问题,例如之前的收藏,是有星星图标的现在变成系统默认的对勾了。这个时候尝试使用AppCompatRadioButton,但是并没有改善,这时候fack source吧,于是在之前的虚拟机机把之间的aosp里同步一下androidX-master-dev,但是奈何虚拟机器看androidx-master-dev实在是太卡,于是新建里Mac区分大小写的分区,现将AOSP下载下来,后再下载androidX-master-dev

最后按照androidx-master-dev说的将环境,搭建好后,调试androidx-master-dev,以下是AppCompatCheckBox源码分析

从layout xml创建AppCompatCheckBox

1
2
3
4
5
6
7
8
9
10
11
12
public class AppCompatCheckBox {
private final AppCompatCompoundButtonHelper mCompoundButtonHelper;
public AppCompatCheckBox(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.checkboxStyle);
}
public AppCompatCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
//....
mCompoundButtonHelper = new AppCompatCompoundButtonHelper(this);
mCompoundButtonHelper.loadFromAttributes(attrs, defStyleAttr);
//....
}
}

style引用和继承

其中R.attr.checkboxStyle是一个应用,在Android 4.4.4的系统中引用的是
support/appcompat/appcompat/src/main/res/values/themes_base.xml中定义的

1
2
3
<item name="checkboxStyle">@style/Widget.AppCompat.CompoundButton.CheckBox</item>
<item name="radioButtonStyle">@style/Widget.AppCompat.CompoundButton.RadioButton</item>
<item name="switchStyle">@style/Widget.AppCompat.CompoundButton.Switch</item>

其中@style/Widget.AppCompat.CompoundButton.CheckBox是在
support/appcompat/appcompat/src/main/res/values/styles.xml

1
<style name="Widget.AppCompat.CompoundButton.CheckBox" parent="Base.Widget.AppCompat.CompoundButton.CheckBox" />

而Base.Widget.AppCompat.CompoundButton.CheckBox是在
path:aosp/frameworks/support/appcompat/appcompat/src/main/res/values/styles_base.xml,下面这个style,兼容库中的style

1
2
3
4
5
<style name="Base.Widget.AppCompat.CompoundButton.CheckBox" parent="android:Widget.CompoundButton.CheckBox">
<item name="android:button">?android:attr/listChoiceIndicatorMultiple</item>
<item name="buttonCompat">?attr/listChoiceIndicatorMultipleAnimated</item>
<item name="android:background">?attr/controlBackground</item>
</style>

他的父style再往下走就是Android SDK里定义的了
path:aosp/prebuilts/fullsdk-darwin/platforms/android-29/data/res/values/style.xml里定义的

1
2
3
<style name="Widget.CompoundButton.CheckBox">
<item name="button">?attr/listChoiceIndicatorMultiple</item>
</style>

加载兼容库中的style

可以看到在兼容库中的style自带android:button,buttonCompat,background属性
而在AppCompatCheckBox三个参数的构造方法中会,将兼容库中的style通过mCompoundButtonHelper.loadFromAttributes(attrs, defStyleAttr)将这个样式的内容加载到view中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 在原有的compat checkbox style基础上添加
TintTypedArray a =
TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
R.styleable.CompoundButton, defStyleAttr, 0);
try {
boolean buttonDrawableLoaded = false;
// 设置compat checkbox style中buttonCompat的引用到ButtonDrawable
if (a.hasValue(R.styleable.CompoundButton_buttonCompat)) {
final int resourceId = a.getResourceId(R.styleable.CompoundButton_buttonCompat, 0);
if (resourceId != 0) {
try {
mView.setButtonDrawable(
AppCompatResources.getDrawable(mView.getContext(), resourceId));
buttonDrawableLoaded = true;
} catch (Resources.NotFoundException nfe) {
// Animated buttonCompat relies on AAPT2 features. If not found then swallow
// this error and fall back to the regular drawable.
}
}
}
if (!buttonDrawableLoaded && a.hasValue(R.styleable.CompoundButton_android_button)) {
final int resourceId = a.getResourceId(
R.styleable.CompoundButton_android_button, 0);
if (resourceId != 0) {
mView.setButtonDrawable(
AppCompatResources.getDrawable(mView.getContext(), resourceId));
}
}
.......
} finally {
a.recycle();
}
}

结论

TintTypedArray.obtainStyledAttributes方法的作用是将自己希望的得到属性返回回来,其中CompoundButton就有buttonCompat,就首先会加载buttonCompat,然后不在加载android:button,而这些属性都是compat checkbox style里写死的无论我们在Android 4.4.4怎么改xml,都不会改变android:button属性。

在其他CompoundButton的子类也会有类似的问题

解决方法

首先看看CheckBox

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CheckBox extends CompoundButton {
public CheckBox(Context context) {
this(context, null);
}

public CheckBox(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.checkboxStyle);
}

public CheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}

public CheckBox(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

@Override
public CharSequence getAccessibilityClassName() {
return CheckBox.class.getName();
}
}

我们只需要简单继承一下CheckBox可以解决

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SpaxCheckBox extends CheckBox {
public SpaxCheckBox(Context context) {
super(context);
}

public SpaxCheckBox(Context context, AttributeSet attrs) {
super(context, attrs);
}

public SpaxCheckBox(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
}

疑问

实在是想不明白,为啥会我直接在xml里写的CheckBox,照理会加载AOSP内部的checkBoxStyle的呀。我直接继承CheckBox,如果按照我之前理解的可能是style冲突,加载了app的style,但是这我直接继承的也按照之前的猜想也会加载app的style。但是实际上是加载的AOSP里的style。

这是时候我们通过debug可以看出,其实XML的CheckBox已换成AppCompatCheckBox了,而我们直接继承的CheckBox并在layout xml中使用了,这时候是会加载AOSP内部的checkBoxStyle。

Debug