Android Android-skin-support 换肤方案 原理讲解

文章目录

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

前言

请先查看这两篇文章

  • 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 根据不同皮肤 返回不同的颜色值 (只说思路,还有很多功能)

你可能感兴趣的:(Android,View篇)