首先代码基于上一节的内容来实现的,先看一下效果:
看一下代码的目录结构:
ISkinable:
/**
* @author Eason
* @createtime 2020/3/19
* @desc 皮肤切换的接口,所有需要修改日/夜间模式的都要实现这个接口
*/
public interface ISkinable {
//在接口中的变量都默认是public static final的全局常量
int[] mAttrs = R.styleable.SkinAllAttr;
/**
* 切换模式
*/
void changeSkin();
}
public class MyAppCompatViewInflater extends AppCompatViewInflater {
private Context mContext;
public MyAppCompatViewInflater(Context context) {
this.mContext = context;
}
/**
* 自动匹配控件名,并初始化控件对象
*/
public View generateView(String name, AttributeSet attrs) {
View view;
switch (name) {
case "TextView":
view = new SkinTextView(mContext, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = new SkinButton(mContext, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = new SkinImageView(mContext, attrs);
verifyNotNull(view, name);
break;
case "androidx.constraintlayout.widget.ConstraintLayout":
view = new SkinConstraintLayout(mContext, attrs);
verifyNotNull(view, name);
break;
case "RelativeLayout":
view = new SkinRelativeLayout(mContext, attrs);
verifyNotNull(view, name);
break;
case "LinearLayout":
view = new SkinLinearLayout(mContext, attrs);
verifyNotNull(view, name);
break;
//todo 继续添加如 FrameLayout、自定义View...
default:
view = super.createView(mContext, name, attrs);
}
return view;
}
/**
* 校验控件不为空(源码方法,由于private修饰,只能复制过来了。为了代码健壮,可有可无)
*
* @param view 被校验控件,如:AppCompatTextView extends TextView(v7兼容包,兼容是重点!!!)
* @param name 控件名,如:"ImageView"
*/
private void verifyNotNull(View view, String name) {
if (view == null) {
throw new IllegalStateException(this.getClass().getName()
+ " asked to inflate view for <" + name + ">, but returned null");
}
}
}
ActionBarUtils:
public class ActionBarUtils {
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static void setColor(AppCompatActivity activity) {
TypedArray a = activity.getTheme().obtainStyledAttributes(0, new int[]{
android.R.attr.colorPrimary
});
int color = a.getColor(0, 0);
a.recycle();
ActionBar actionBar = activity.getSupportActionBar();
if (actionBar != null) {
actionBar.setBackgroundDrawable(new ColorDrawable(color));
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static void setColor(AppCompatActivity activity, int skinColor) {
ActionBar actionBar = activity.getSupportActionBar();
if (actionBar != null) {
actionBar.setBackgroundDrawable(new ColorDrawable(skinColor));
}
}
}
SkinButton:
/**
* 继承TextView兼容包,9.0源码中也是如此
* 参考:AppCompatViewInflater.java
* 86行 + 138行 + 206行
*/
public class SkinButton extends AppCompatButton implements ISkinable {
private SparseIntArray mResourceMap = new SparseIntArray();
public SkinButton(Context context) {
this(context, null);
}
public SkinButton(Context context, AttributeSet attrs) {
//TODO 这里需要更改
this(context, attrs, android.R.attr.buttonStyle);
}
public SkinButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 根据自定义属性,匹配控件属性的类型集合,如:background + textColor
TypedArray a = context.obtainStyledAttributes(attrs, mAttrs, defStyleAttr, 0);
//R.styleable.SkinTextView 为在attrs定义的属性值数组
for (int i = 0; i < mAttrs.length; i++) {
mResourceMap.put(mAttrs[i], a.getResourceId(i, -1));
}
a.recycle();
}
@Override
public void changeSkin() {
//设置background
setBackground();
//设置textColor
setTextColor();
//设置字体
setTextFont();
}
/**
* 设置字体
*/
private void setTextFont() {
if (SkinManager.getInstance().isOpenFontChange()) {
int textFontId = mAttrs[R.styleable.SkinAllAttr_custom_typeface];
int textTypefaceResourceId = mResourceMap.get(textFontId);
//有一种情况是,如果在宿主app内没有定义custom_typeface这个字段,则textTypefaceResourceId返回-1,字体不生效
if (textTypefaceResourceId > 0) {
if (SkinManager.getInstance().isLoadDefault()) {
setTypeface(Typeface.DEFAULT);
} else {
setTypeface(SkinManager.getInstance().getTypeface(textTypefaceResourceId));
}
}
}
}
/**
* 设置字体颜色
*/
private void setTextColor() {
int textColorKey = mAttrs[R.styleable.SkinAllAttr_android_textColor];
int textColorId = mResourceMap.get(textColorKey);
if (textColorId > 0) {
if (SkinManager.getInstance().isLoadDefault()) {
setTextColor(ContextCompat.getColorStateList(getContext(), textColorId));
} else {
setTextColor(SkinManager.getInstance().getColor(textColorId));//注意这里返回是直接可以使用的
}
}
}
/**
* 设置背景
*/
private void setBackground() {
int backgroundKey = mAttrs[R.styleable.SkinAllAttr_android_background];
int backgroundId = mResourceMap.get(backgroundKey);
if (backgroundId > 0) {
if (SkinManager.getInstance().isLoadDefault()) {
setBackgroundDrawable(ContextCompat.getDrawable(getContext(), backgroundId))