文章目录

    • 前言
    • 思考一下
    • 开源库中 找到答案
    • 结束语

前言

请先查看这两篇文章

  • LayoutInflater.Factory
    • Android xml解析到View的过程
    • Android 无需自定义View 解放 shape 解决方案 原理讲解

思考一下

上面已经说明了,我们自定义Factory2 就可以达到 无需shape的解决方案

那么同理,换肤我们怎么做呢?

先整理一下思路

  1. 自定义两个 color 的值 分别是
 #ff000000 #ffffffff // 也就是main_color 白天的时候为 黑色 // 夜间的时候 为 白色
  1. 在自定义的 Factory2 (如WidSkinFactory2)中 创建自己的View 比如模仿 AppCompatActivity中 createView真正操作的类 AppCompatViewInflater.java
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);    }}
  1. 既然 View 创建成我们自己的 比如 SkinCompatTextView , 那么我们不就可以轻松实现 属性获取,比如字体颜色 然后我们根据不同皮肤,加载不同颜色
  2. 这样不就可以换肤了

问题:

那么如果我们想动态换肤,怎么办?

耶? 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
然后返回

当然这个类还有很多功能,我们这里只研究重点

结束语

  1. Android-skin-support 也是通过 自定义Factory的方式 实现
  2. 将系统传过来的 view 如 TextView 创建成 自己的 SkinCompatTextView
  3. 通过 xxx_Helper 进行资源操作,并对外提供 applySkin 换肤方法
  4. SkinCompatResources 根据不同皮肤 返回不同的颜色值 (只说思路,还有很多功能)

更多相关文章

  1. Android短信转发默认不需要转发号码修改方法
  2. Android实现ListView或GridView首行/尾行距离屏幕边缘距离
  3. Android(安卓)viewPager实现翻动
  4. Android高手进阶教程(十五)---Android中万能的BaseAdapter(Spinn
  5. [转载]Android实现更换皮肤功能
  6. Android高手进阶教程(十六)之---Android中万能的BaseAdapter(Spi
  7. Android(安卓)Studio LibraryModule中引用aar
  8. 菜鸟的安卓实习之路---- 如何实现android恢复出厂设置
  9. android沉浸式全屏显示

随机推荐

  1. Android开发之API应用指南
  2. Phonegap获取imei (修改phonegap官方提供
  3. Android学习:LogCat日志查询
  4. 简易版的 Spring 之如何实现 Setter 注入
  5. Android中的四大组件
  6. Android(安卓)补间动画 --旋转
  7. Android中sqlite事物控制
  8. Android开发之自定义Notification(源代码
  9. Work 工作子线程更新UI控件--解决Only th
  10. Android(安卓)studio将项目转换为jar文件