安卓插件化之插件式换肤(2)

安卓插件化之插件式换肤(2)


安卓插件化之插件式换肤(1)

管理类的搭建

1 皮肤管理类 SkinManager
2 皮肤资源 SkinResource
3 皮肤支持类 SkinSupport
回到我们的BaseActivity

 @Nullable
    @Override
    public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
//        1.创建View
        View view = createView(parent, name, context, attrs);
//        2.获取皮肤资源
        if (view != null) {
           Log.e("TAG", "onCreateView: --》"+view );
           //2.1 重支持类中加载皮肤
           List<SkinAttr> mSkinAttrs = SkinSupport.getSkinAttrs(context, attrs);
//        创建皮肤View
           SkinView skinView = new SkinView(view, mSkinAttrs);
//        3交给SkinManager管理
            managerSkinView(skinView);
          // 4 判断要不要换肤
          SkinManager.getInstance().checkSkinView(skinView);

        }
        return view;
    }

所以我们先去SkinSupport 里面完善

package com.li.skinlibrary.skin;

import android.content.Context;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;

import com.li.skinlibrary.skin.attribute.SkinAttr;
import com.li.skinlibrary.skin.attribute.SkinType;

import java.util.ArrayList;
import java.util.List;

/**
 * @author li
 * 版本:1.0
 * 创建日期:2020/6/30 09
 * 描述:皮肤解析支持类
 */
public class SkinSupport {
    public static List<SkinAttr> getSkinAttrs(Context context, AttributeSet attrs) {
        //1.创建List
        List<SkinAttr> skinAttrs = new ArrayList<>();
        //2 创建skinAttr
        for (int i = 0; i < attrs.getAttributeCount(); i++) {
            String AttrName = attrs.getAttributeName(i);
            String Value = attrs.getAttributeValue(i);
//            Log.e("TAG", "getSkinAttrs: -->" + AttrName + "..." + Value);
//            textColor...@2130968616
            //获取我们需要的skinType 不是我们需要的不需要换肤
            SkinType mSkinType = getSkinTypes(AttrName);
            if (mSkinType!=null){
                String ResName = getSkinResName(context,Value);
                if (TextUtils.isEmpty(ResName)){
                    continue;
                }
                SkinAttr skinAttr = new SkinAttr(ResName,mSkinType);
                skinAttrs.add(skinAttr);
            }

        }

        return skinAttrs;
    }
	//  textColor...@2130968616我们得到的是地址,需要找到我们需要的文件名
    private static String getSkinResName(Context context, String value) {
        if (value.startsWith("@")){
            value = value.substring(1);
            String resName = context.getResources().getResourceEntryName(Integer.parseInt(value));
//            Log.e("TAG", "getSkinResName: -->"+resName );
            return resName;
        }
        return null;
    }

    private static SkinType getSkinTypes(String resName) {
        SkinType [] skinTypes = SkinType.values();
        for (SkinType skinType : skinTypes) {
            if (skinType.getResName().equals(resName)){
                return  skinType;
            }
        }

        return null;
    }
}

皮肤支持类基本上完成目的就是找到View里面我们需要修改的控件和属性

接下来我们需要管理我们的皮肤,皮肤管理类的完善

  private void managerSkinView(SkinView skinView) {
  		//在管理类里获取到需要的view的列表
        List<SkinView> skinViews = SkinManager.getInstance().getSkinViews(this);
        if (skinViews == null) {
        //是空的话创建
            skinViews = new ArrayList<>();
            SkinManager.getInstance().registerSkinView(this, skinViews);
        }
        //添加到List里面
        skinViews.add(skinView);
    }

到我们的SkinManager了 在写管理类之前,考虑到对于一些自定义view或者一些有特殊设置的控件来说,我门能做到的有限,但是如果不管这些东西的话,又缺失一些功能,所以在完成管理类的时候我们暴露一个接口,在换肤的时候回调过去,这样我们就能够适配大多数控件的同时,增加我们的延展性

package com.li.skinlibrary.skin.call;

import com.li.skinlibrary.skin.SkinResource;

/**
 * @author li
 * 版本:1.0
 * 创建日期:2020/6/30 20
 * 描述:换肤回调接口--------完成一些无法切换的类
 */
public interface SkinChangeCallBack {
    void skinChange(SkinResource skinResource);
}

所以在基础Activity中添加接口回调以便自定义View等其他属性的view换肤使用

public abstract class BaseActivity extends AppCompatActivity implements SkinChangeCallBack {
	//在baseActivity中实现这个接口
	  /**
     * @description 切换皮肤回调
     * @param 
     */
    @Override
    public void skinChange(SkinResource skinResource) {

    }
}

SkinManager


/**
 * @author li
 * 版本:1.0
 * 创建日期:2020/6/30 09
 * 描述:
 */
public class SkinManager {
    private static volatile SkinManager mInstance;
    private Context mContext;

    private SkinResource mSkinResource;
    private Map<SkinChangeCallBack,List<SkinView>> mViewMap = new HashMap<>();

    private SkinManager() {

    }

    public static SkinManager getInstance() {
        if (mInstance == null) {
            synchronized (SkinManager.class) {
                if (mInstance == null) {
                    mInstance = new SkinManager();
                }
            }
        }
        return mInstance;
    }
	//皮肤资源初始化,后面有介绍
    public void init(Context context) {
        this.mContext = context.getApplicationContext();
        //打开应用会立刻初始化如果有皮肤加载需要初始化SkinResource
        SkinLoadUtils.getInstance().init(context);
        String currentSkinPath = SkinLoadUtils.getInstance().getLoadPath();
        File file = new File(currentSkinPath);
        if (!file.exists()){
            SkinLoadUtils.getInstance().cleanLoadPath();
            return ;
        }
        String mPackageName = context.getPackageManager().getPackageArchiveInfo(currentSkinPath, PackageManager.GET_ACTIVITIES).packageName;
        if (TextUtils.isEmpty(mPackageName)){
            SkinLoadUtils.getInstance().cleanLoadPath();
            return ;
        }
        mSkinResource = new SkinResource(mContext, currentSkinPath);
    }


    public void loadResource(String path) {
//       判断 path 文件是否存在
        File file = new File(path);
        if (!file.isFile()){
            SkinLoadUtils.getInstance().cleanLoadPath();
            return;
        }
//        判断是否是APK文件,包名是否存在
        String mPackageName = mContext.getPackageManager().getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES).packageName;
        if (TextUtils.isEmpty(mPackageName)){
            SkinLoadUtils.getInstance().cleanLoadPath();
            return ;
        }
//       判断是否是已经载的的地址
        String currentSkinPath = SkinLoadUtils.getInstance().getLoadPath();
        if (!TextUtils.isEmpty(currentSkinPath)){
            if (currentSkinPath.equals(path)){
                return;
            }
        }
        //初始化资源
        mSkinResource = new SkinResource(mContext, path);
//       加载皮肤
        loadActivitySkin();
//       保存皮肤
        saveSkinLoadPath(path);
    }

    private void loadActivitySkin() {
        Set<SkinChangeCallBack> skinChangeCallBacks =mViewMap.keySet();
        for (SkinChangeCallBack callBack : skinChangeCallBacks) {
            List<SkinView> skinViews = mViewMap.get(callBack);
            if (skinViews!=null){
                for (SkinView skinView : skinViews) {
                    skinView.loadSkin();
                }
                callBack.skinChange(mSkinResource);
            }
        }
    }

    private void saveSkinLoadPath(String path) {
        SkinLoadUtils.getInstance().saveLoadPath(path);
    }


    public List<SkinView> getSkinViews(SkinChangeCallBack callBack) {
        return mViewMap.get(callBack);
    }

    public void registerSkinView(SkinChangeCallBack skinChangeCallBack ,List<SkinView> skinViews) {
        mViewMap.put(skinChangeCallBack,skinViews);
    }

    public SkinResource getSkinResource() {
        return mSkinResource;
    }
    /**
     * @description 恢复默认
     * @param
     */
    public void recoverDefault() {
        String currentSkinPath = SkinLoadUtils.getInstance().getLoadPath();
        if (TextUtils.isEmpty(currentSkinPath)){
            return;
        }
        String mPackageResourcePath = mContext.getPackageResourcePath();
        SkinLoadUtils.getInstance().cleanLoadPath();
        mSkinResource = new SkinResource(mContext,mPackageResourcePath);
        loadActivitySkin();
    }

    /**
     * @description 检测是否需要换肤
     * @param 
     */
    public void checkSkinView(SkinView skinView) {
        //保存的皮肤就换肤
        if (!TextUtils.isEmpty(SkinLoadUtils.getInstance().getLoadPath())) {
//            Log.e("TAG", "checkSkinView: -->"+SkinLoadUtils.getInstance().getLoadPath());
            skinView.loadSkin();
        }
    }
    /**
     * @description 解绑
     * @param
     */
    public void unBinderSkinActivity(SkinChangeCallBack skinChangeCallBack) {
        mViewMap.remove(skinChangeCallBack);
    }
}

SkinResource就需要用到之前源码的一点知识了通过反射拿到皮肤资源
AssetManager里面 addAssetPath 的方法在拿到资源

/**
 * @author li
 * 版本:1.0
 * 创建日期:2020/6/30 09
 * 描述:皮肤资源
 */
public class SkinResource {
    private Resources mResource;
    private String mPackageName;
    public SkinResource(Context context ,String path) {
//        获取本地资源
        try {
            Resources superRes = context.getResources();
            AssetManager asset  =AssetManager.class.newInstance();
            Method method  = AssetManager.class.getDeclaredMethod("addAssetPath",String.class);
            method.setAccessible(true);
            method.invoke(asset,path);
            mResource = new Resources(asset,superRes.getDisplayMetrics(),superRes.getConfiguration());
            mPackageName = context.getPackageManager().getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES).packageName;
        } catch ( Exception e) {
            e.printStackTrace();
        }

    }

    public ColorStateList getColorByName(String resName) {
        int resId = mResource.getIdentifier(resName,"color",mPackageName);
        ColorStateList  colorStateList = null;
        try {
            colorStateList = mResource.getColorStateList(resId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return colorStateList;
    }

    public Drawable getDrawableByName(String resName) {
        int resId = mResource.getIdentifier(resName,"drawable",mPackageName);
        Drawable drawable = null;
        try {
            drawable = mResource.getDrawable(resId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return drawable;
    }

    public String getTextRes(String resName) {
        int resId = mResource.getIdentifier(resName,"string",mPackageName);
        String text = null;
        try {
            text = mResource.getString(resId);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return text;
    }
}

初始化与保存

在BaseApplication 初始化SkinManager

public class BaseApplication extends Application {
    public static Context getContext() {
        return getContext().getApplicationContext();
    }

    @Override
    public void onCreate() {
        super.onCreate();
        SkinManager.getInstance().init(this);

    }
}

上面我将SkinManager的代码完全粘了出来在这里在详细的解释一下
因为皮肤的保存需要下次登录自动更换,所以我门需要将SkinManager在一登录就初始化 同时还需要将SkinResource 初始化,因为如果不初始化如需要换肤会出现空指针的情况 同时我们加入了皮肤保存加载Utils ——SkinLoadUtils 来处理皮肤的保存和清空

/**
 * @author li
 * 版本:1.0
 * 创建日期:2020/6/30 18
 * 描述:皮肤保存加载Utils
 */
public  class SkinLoadUtils {
    private static volatile SkinLoadUtils mInstance;
    private Context mContext;
    public SkinLoadUtils() {
    }

    public static SkinLoadUtils getInstance() {
        if (mInstance==null){
            synchronized (SkinLoadUtils.class){
                if (mInstance==null){
                    mInstance = new SkinLoadUtils();
                }
            }
        }
        return mInstance;
    }

    public void init(Context context){
        this.mContext = context;
    }

    /**
     * @description 保存路径
     * @param
     */
    public void saveLoadPath(String path){
        mContext.getSharedPreferences(SkinConfig.SKIN_CONFIG_PATH,Context.MODE_PRIVATE).edit()
                .putString(SkinConfig.SKIN_LOAD_PATH,path).commit();
    }
    /**
     * @description 返回当前皮肤路径
     * @param 
     */
    public String getLoadPath(){
      return mContext.getSharedPreferences(SkinConfig.SKIN_CONFIG_PATH,Context.MODE_PRIVATE)
              .getString(SkinConfig.SKIN_LOAD_PATH,"");
    }
    /**
     * @description 清除路径
     * @param 
     */
    public void cleanLoadPath (){
        mContext.getSharedPreferences(SkinConfig.SKIN_CONFIG_PATH,Context.MODE_PRIVATE).edit()
                .putString(SkinConfig.SKIN_LOAD_PATH,"").commit();
    }
}

SKinConfig的加入只是为了使结构更严谨 方便以后添加config

/**
 * @author li
 * 版本:1.0
 * 创建日期:2020/6/30 18
 * 描述:
 */
public class SkinConfig {

    /**保存SharedPreferences的名称*/
    public static String SKIN_CONFIG_PATH = "skinConfigPath";
    /**皮肤路径*/
    public static String SKIN_LOAD_PATH = "skinLoadPath";
}

最后内存泄露问题

因为我们是单例模式引用Activity里面的View 如果在退出的时候不讲View解绑会照成activity一直被单例所引用而无法被GC回收掉,所以在BaseActivity的ondestroy声明周期我们加入

   @Override
    protected void onDestroy() {
        SkinManager.getInstance().unBinderSkinActivity(this);
        super.onDestroy();
    }

来进行解绑,避免因无法回收造成的内存泄露
源码

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值