Android换肤调研

背景

Android换肤技术已经是很久之前就已经被成熟使用的技术了,公司业务上需要用到换肤.为了不重复造轮子,并且快速实现需求,并且求稳,发现主要有两个框架比较流行,Android-Skin-Loader和Android-skin-support

Android-Skin-Loader

GitHub - fengjundev/Android-Skin-Loader: 一个通过动态加载本地皮肤包进行换肤的皮肤框架
在这里插入图片描述
可以看到好几年都没人维护了,出了问题也不好解决(这里没有丝毫贬低该框架的意思)

这里大概说下原理,通过LayoutInflater.setFactory的方式, 在回调的onCreateView中解析每一个View的attrs, 判断是否有已标记需要换肤的属性, 比方说background, textColor, 或者说相应资源是否为skin_开头等等. 然后保存到map中, 对每一个View做for循环去遍历所有的attr, 想要对更多的属性进行换肤, 需要Activity实现接口, 将需要换肤的View, 以及相应的属性收集到一起

Android-skin-support

[外链图片转存失败(img-N53580LB-1569138768035)(:storage/b880fd6d-37c9-45b8-894a-22181a6c4adf/aab99155.png)]

Github上一个star数比较多的换肤框架-Android-skin-support(一款用心去做的Android 换肤框架, 极低的学习成本, 极好的用户体验. 一行代码就可以实现换肤, 你值得拥有!!!). 简单了解之后,可以快速上手,并且侵入性很低,源码地址: https://github.com/ximsfei/Android-skin-support

介绍

SkinCompatManager.withoutActivity(this).loadSkin();
就这么简单, 你的APK已经拥有了强大的换肤功能, 当然现在是拥有了换肤功能, 别忘了制作皮肤包.

功能

支持布局中用到的资源换肤。
支持代码中设置的资源换肤。
默认支持大部分基础控件,Material Design换肤。
支持动态设置主题颜色值,支持选择sdcard上的图片作为drawable换肤资源。
支持多种加载策略(应用内/插件式/自定义sdcard路径/zip等资源等)。
资源加载优先级: 动态设置资源-加载策略中的资源-插件式换肤/应用内换肤-应用资源。
支持定制化,选择需要的模块加载。
支持矢量图(vector/svg)换肤。
skin-support 4.0.0以上支持AndroidX,4.0.0以下支持support库
更详细的信息可以直接参考官方说明,很详细

那么它是如何实现换肤的呢,下面先来点预备知识

AppCompatActivity实现

吐槽一下,Google为了让开发者升级androidx,support28版本很多库都不提供源码,大家可能也发现了,好了回到正题

public class AppCompatActivity extends FragmentActivity {
   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       final AppCompatDelegate delegate = getDelegate();
       delegate.installViewFactory();
       delegate.onCreate(savedInstanceState);
       ...
   }

   @Override
   public MenuInflater getMenuInflater() {
       return getDelegate().getMenuInflater();
   }

   @Override
   public void setContentView(@LayoutRes int layoutResID) {
       getDelegate().setContentView(layoutResID);
   }

   @Override
   public void setContentView(View view) {
       getDelegate().setContentView(view);
   }
   ....
}

AppCompatActivity 将大部分生命周期委托给了AppCompatDelegate

类图
源码中主要使用了AppCompateDelegate的子类AppCompatDelegateImpl

class AppCompatDelegateImpl extends AppCompatDelegate implements Callback, Factory2

 public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
        if (layoutInflater.getFactory() == null) {
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
            Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
        }

    }

AppCompatDelegateImpl中, 在LayoutInflaterFactory的接口方法onCreateView 中将View的创建交给了AppCompatViewInflater

public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
        if (this.mAppCompatViewInflater == null) {
            TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
            String viewInflaterClassName = a.getString(styleable.AppCompatTheme_viewInflaterClass);
            if (viewInflaterClassName != null && !AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
                try {
                    Class viewInflaterClass = Class.forName(viewInflaterClassName);
                    this.mAppCompatViewInflater = (AppCompatViewInflater)viewInflaterClass.getDeclaredConstructor().newInstance();
                } catch (Throwable var8) {
                    Log.i("AppCompatDelegate", "Failed to instantiate custom view inflater " + viewInflaterClassName + ". Falling back to default.", var8);
                    this.mAppCompatViewInflater = new AppCompatViewInflater();
                }
            } else {
                this.mAppCompatViewInflater = new AppCompatViewInflater();
            }
        }

        boolean inheritContext = false;
        if (IS_PRE_LOLLIPOP) {
            inheritContext = attrs instanceof XmlPullParser ? ((XmlPullParser)attrs).getDepth() > 1 : this.shouldInheritContext((ViewParent)parent);
        }
       //可以直接看这里
        return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());
    }

再来看一下AppCompatViewInflater中createView的实现

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;
    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);
  
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值