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(); }来进行解绑,避免因无法回收造成的内存泄露 源码