请先查看这两篇文章
上面已经说明了,我们自定义Factory2 就可以达到 无需shape的解决方案
那么同理,换肤我们怎么做呢?
先整理一下思路
#ff000000
#ffffffff
// 也就是main_color 白天的时候为 黑色
// 夜间的时候 为 白色
public class AppCompatViewInflater {
final View createView(...){
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
default:
break;
}
}
@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
}
问题:
那么如果我们想动态换肤,怎么办?
耶? View都是我们自己创建的了,那么我们在 自定义Factory2 (如WidSkinFactory2)中,记录下这些 View,然后定义某个方法,刷新新的皮肤不就好了?
https://github.com/ximsfei/Android-skin-support
为了验证我们的想法更或者说是看看别人是否有更好的解决方案
我们来 提取关键几个类进行讲解
查看Application
这里的源码 其实就是注册一个 Application.ActivityLifecycleCallbacks
在 Lifecycle onActivityCreated() 方法中 进行 installLayoutFactory
也就是将系统的 Factory2 替换为 自己的 SkinCompatDelegate <继承于 LayoutInflaterFactory>
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
// 框架换肤日志打印
Slog.DEBUG = true;
SkinCompatManager.withoutActivity(this)
.addInflater(new SkinAppCompatViewInflater()) // 基础控件换肤
...
.loadSkin();
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
}
}
SkinCompatDelegate.java
public class SkinCompatDelegate implements LayoutInflaterFactory {
private final Context mContext;
private SkinCompatViewInflater mSkinCompatViewInflater;
private List> mSkinHelpers = new ArrayList<>();
private SkinCompatDelegate(Context context) {
mContext = context;
}
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View view = createView(parent, name, context, attrs);
if (view == null) {
return null;
}
// 缓存 View 这里跟我们 上面思考的问题一样
// 记录View 用于后期动态换肤
if (view instanceof SkinCompatSupportable) {
mSkinHelpers.add(new WeakReference<>((SkinCompatSupportable) view));
}
return view;
}
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mSkinCompatViewInflater == null) {
mSkinCompatViewInflater = new SkinCompatViewInflater();
}
...
mSkinCompatViewInflater.createView(parent, name, context, attrs);
}
//换肤
public void applySkin() {
if (mSkinHelpers != null && !mSkinHelpers.isEmpty()) {
for (WeakReference ref : mSkinHelpers) {
if (ref != null && ref.get() != null) {
((SkinCompatSupportable) ref.get()).applySkin();
}
}
}
}
}
SkinCompatViewInflater.java
public class SkinCompatViewInflater {
private View createViewFromHackInflater(Context context, String name, AttributeSet attrs) {
View view = null;
// 根据 你在 application 中 add 的 Infater 循环判断,是否创建View成功
for (SkinLayoutInflater inflater : SkinCompatManager.getInstance().getHookInflaters()) {
view = inflater.createView(context, name, attrs);
if (view == null) {
continue;
} else {
break;
}
}
return view;
}
}
这里我们先分析个最简单的
SkinAppCompatViewInflater.java
public class SkinAppCompatViewInflater{
switch (name) {
// 创建自己的View,也就是印证了 思考 <2>
case "TextView":
view = new SkinCompatTextView(context, attrs);
}
}
SkinCompatTextView.java
public class SkinCompatTextView extends AppCompatTextView implements SkinCompatSupportable {
private SkinCompatTextHelper mTextHelper;
private SkinCompatBackgroundHelper mBackgroundTintHelper;
public SkinCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 换肤的帮助类,单一原则
mBackgroundTintHelper = new SkinCompatBackgroundHelper(this);
mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper = SkinCompatTextHelper.create(this);
mTextHelper.loadFromAttributes(attrs, defStyleAttr);
}
// 动态换肤
@Override
public void applySkin() {
if (mBackgroundTintHelper != null) {
mBackgroundTintHelper.applySkin();
}
if (mTextHelper != null) {
mTextHelper.applySkin();
}
}
}
SkinCompatTextHelper.java
public class SkinCompatTextHelper {
public void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
// ...
final Context context = mView.getContext();
a = context.obtainStyledAttributes(attrs, R.styleable.SkinTextAppearance, defStyleAttr, 0);
// 颜色
if (a.hasValue(R.styleable.SkinTextAppearance_android_textColor)) {
mTextColorResId = a.getResourceId(R.styleable.SkinTextAppearance_android_textColor, INVALID_ID);
}
a.recycle();
// 执行
applySkin();
}
@Override
public void applySkin() {
...
applyTextColorResource();
}
private void applyTextColorResource() {
mTextColorResId = checkResourceId(mTextColorResId);
// 是否有ID
if (mTextColorResId != INVALID_ID) {
// 通过 工具 传入 id 比如上面思考的 main_color
// 如果是夜间 他就会 返回 main_color_night
ColorStateList color = SkinCompatResources.getColorStateList(mView.getContext(), mTextColorResId);
mView.setTextColor(color);
}
}
}
SkinCompatResources 的源码就不讲解了,就是上面所说的
根据不同的模式,将原有ID_xxx模式,如 main_color_night
然后返回
当然这个类还有很多功能,我们这里只研究重点