android 换肤框架详解3-自动换肤原理梳理

前面已经介绍了换肤的基本逻辑和LayoutInflater源码解析传送门如下

android 换肤框架详解1-换肤逻辑基本-CSDN博客

android 换肤框架详解2-LayoutInflater源码解析-CSDN博客

android 换肤框架详解3-自动换肤原理梳理-CSDN博客

接下来就是整体自动换肤逻辑了

代码如下

  • 自定义LayoutInflater

    import android.content.Context;
    import android.view.LayoutInflater;

    public class SkinLayoutInflaterT extends LayoutInflater {

    复制代码
      protected SkinLayoutInflaterT(LayoutInflater original, Context newContext) {
          super(original, newContext);
          //创建解析接口监听类
          setFactory2(new SkinLayoutInflaterFactoryT());
      }
    
      @Override
      public LayoutInflater cloneInContext(Context newContext) {
          return new SkinLayoutInflaterT(this, newContext);
      }
    
      public static LayoutInflater from(Context context) {
          LayoutInflater original = LayoutInflater.from(context);
          return new SkinLayoutInflaterT(original, context);
      }

    }

  • 创建监听类LayoutInflater.Factory2

    import android.content.Context;
    import android.text.TextUtils;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.LayoutInflater;
    import android.view.View;

    import java.lang.reflect.Field;
    import java.util.HashMap;

    public class SkinLayoutInflaterFactoryT implements LayoutInflater.Factory2 {
    public static final String TAG = "SkinLayoutInflaterFactoryT";

    复制代码
      /**
       * 在解析创建的时候,会执行这方法,在这个方法创建View并且保存对应信息
       */
      @Override
      public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
          Log.d(TAG, "SkinInflaterFactory onCreateView(), create view name=" + name + "  ");
          //自己解析创建View
          View view = createView(context, name, attrs);
          //获取xml中所有属性,进行解析
          int count = attrs.getAttributeCount();
          HashMap<String, SkinAttrT> hashMap = null;
          for (int i = 0; i < count; i++) {
              //解析属性
              String attributeName = attrs.getAttributeName(i);// 属性名,如 "layout_width"
              String attributeValue = attrs.getAttributeValue(i); // 属性值,如 "match_parent"
              //资源文件获取到的值前面会加一个@,这里判断属性值是否是资源文件
              if (!attributeValue.startsWith("@")) {
                  continue;
              }
              //判断是否在自己已经支持的View设置方法
              if (isSupport(attributeName)) {
                  //拿到Res值
                  int resId = Integer.parseInt(attributeValue.substring(1));
                  if (resId == 0) {
                      continue;
                  }
                  if (hashMap == null) {
                      hashMap = new HashMap<>();
                  }
                  //返回格式:ic_launcher(仅资源名)R.drawable.ic_launcher,R.color.ic_launcher
                  String attrValueName = context.getResources().getResourceEntryName(resId);
                  //资源类型类型drawable,color
                  String attrValueType = context.getResources().getResourceTypeName(resId);
                  //创建需要保存的属性和对应的值
                  //这里保存设置方法,ResId值,resId的名字,resId的类型
                  SkinAttrT skinAttrT = new SkinAttrT(attributeName, resId, attrValueName, attrValueType);
                  //一个View的xml中可能有多个设置方法和resId,这里设置
                  hashMap.put(attributeName, skinAttrT);
              }
              Log.d(TAG, "attributeName: " + attributeName + " = " + attributeValue);
          }
          if (view != null && hashMap != null) {
              //将属View和其对应的属性值进行保存
              SkinManagerT.getInstance().saveSkinView(view, hashMap);
          }
          return view;
      }
    
      public boolean isSupport(String attributeName) {
          return SkinConstantT.getSkinConstantSet().contains(attributeName);
      }
    
      @Override
      public View onCreateView(String name, Context context, AttributeSet attrs) {
          return null;
      }
    
      private View createView(Context context, String name, AttributeSet attrs) {
    
          View view = null;
          try {
              LayoutInflater inflater = LayoutInflater.from(context);
              assertInflaterContext(inflater, context);
    
              if (-1 == name.indexOf('.')) {
                  if ("View".equals(name) || "ViewStub".equals(name) || "ViewGroup".equals(name)) {
                      view = inflater.createView(name, "android.view.", attrs);
                  }
                  if (view == null) {
                      view = inflater.createView(name, "android.widget.", attrs);
                  }
                  if (view == null) {
                      view = inflater.createView(name, "android.webkit.", attrs);
                  }
              } else {
                  view = inflater.createView(name, null, attrs);
              }
    
          } catch (Exception ex) {
              Log.e(TAG, "createView(), create view failed" + ex);
              view = null;
          }
          return view;
      }
    
      private void assertInflaterContext(LayoutInflater inflater, Context context) {
    
          Context inflaterContext = inflater.getContext();
          if (inflaterContext == null) {
              setField(inflater, "mContext", context);
          }
    
          //设置mConstructorArgs的第一个参数context
          Object[] constructorArgs = (Object[]) getField(inflater, "mConstructorArgs");
          if (null == constructorArgs || constructorArgs.length < 2) {
              //异常,一般不会发生
              constructorArgs = new Object[2];
              setField(inflater, "mConstructorArgs", constructorArgs);
          }
          //如果mConstructorArgs的第一个参数为空,则设置为mContext
          if (null == constructorArgs[0]) {
              constructorArgs[0] = inflater.getContext();
          }
      }
    
      public static Object setField(Object receiver, String fieldName, Object value) {
    
          try {
              Field field;
              field = findField(receiver.getClass(), fieldName);
              if (field == null) {
                  return null;
              }
              field.setAccessible(true);
              Object old = field.get(receiver);
              field.set(receiver, value);
              return old;
          } catch (IllegalAccessException e) {
              e.printStackTrace();
          } catch (IllegalArgumentException e) {
              e.printStackTrace();
          }
          return null;
      }
    
      private static Field findField(Class<?> clazz, String name) {
    
          try {
              return clazz.getDeclaredField(name);
          } catch (NoSuchFieldException e) {
              if (clazz.equals(Object.class)) {
                  e.printStackTrace();
                  return null;
              }
              Class<?> base = clazz.getSuperclass();
              return findField(base, name);
          }
      }
    
      //获取类的实例的变量的值
      public static Object getField(Object receiver, String fieldName) {
    
          return getField(null, receiver, fieldName);
      }
    
      private static Object getField(String className, Object receiver, String fieldName) {
    
          Class<?> clazz = null;
          Field field;
          if (!TextUtils.isEmpty(className)) {
              try {
                  clazz = Class.forName(className);
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              }
          } else {
              if (receiver != null) {
                  clazz = receiver.getClass();
              }
          }
          if (clazz == null) return null;
    
          try {
              field = findField(clazz, fieldName);
              if (field == null) return null;
              field.setAccessible(true);
              return field.get(receiver);
          } catch (IllegalAccessException e) {
              e.printStackTrace();
          } catch (IllegalArgumentException e) {
              e.printStackTrace();
          } catch (NullPointerException e) {
              e.printStackTrace();
          }
          return null;
      }

    }

  • 创建属性值字段SkinConstantT

    import java.util.HashSet;

    /*
    *已经适配的属性值
    */
    public class SkinConstantT {
    public static final String BACKGROUND = "background";
    public static final String TEXT_COLOR = "textColor";
    public static final String COLOR = "color";

    复制代码
      public static final HashSet<String> skinConstantSet = new HashSet<>();
    
      static {
          skinConstantSet.add(BACKGROUND);
          skinConstantSet.add(TEXT_COLOR);
      }
    
      public static HashSet<String> getSkinConstantSet() {
          return skinConstantSet;
      }

    }

  • 创建属性值保存类SkinAttrT

    import android.util.Log;

    public class SkinAttrT {

    复制代码
      public static final String TAG = "SkinAttrT";
    
      /**
       * 方法名称,比如android:background="@color/content",这里是background
       * 这里用于代码中调用对应的方法,比如调用setBackground
       */
      public String attrName;
    
      /**
       * ResID,比如R文件中的Id,R.color.red=0x-- ,R.drawable.--,这里就是0x--
       * 这里用于默认Resource进行修改值,默认res直接通过这个拿到资源文件
       */
      public int attrValueRefId;
      /**
       * ResID对应的名称,比如android:background="@color/content",这里就是content
       * 这个是换肤资源resource拿对应 的resId,要和attrValueTypeName一起用
       * getIdentifier(
       * attrValueRefName,// 资源名
       * attrValueTypeName,// 资源类型
       * "应用包名"
       * );
       */
      public String attrValueRefName;
      /**
       * ResID类型名称,比如比如android:background="@color/content",这里就是color
       * 这个是换肤资源resource拿对应 的resId,要和attrValueRefId一起用
       * getIdentifier(
       * attrValueRefName,// 资源名
       * attrValueTypeName,// 资源类型
       * "应用包名"
       * );
       */
      public String attrValueTypeName;
    
      public Object[] attrValueFormat;
    
      public SkinAttrT(String attrName, int attrValueRefId, String attrValueRefName, String attrValueTypeName) {
          Log.d(TAG, attrName + "  attrValueRefId = " +
                  attrValueRefId + " attrValueRefName = " + attrValueRefName + " attrValueTypeName = " + attrValueTypeName);
          this.attrName = attrName;
          this.attrValueRefId = attrValueRefId;
          this.attrValueRefName = attrValueRefName;
          this.attrValueTypeName = attrValueTypeName;
      }
    
      public SkinAttrT setAttrValueFormat(Object[] format) {
          this.attrValueFormat = format;
          Log.d(TAG, attrName + "");
          return this;
      }
    
      @Override
      public String toString() {
    
          return "SkinAttr \n[\nattrName=" + attrName + ", \n"
                  + "attrValueRefId=" + attrValueRefId + ", \n"
                  + "attrValueRefName=" + attrValueRefName + ", \n"
                  + "attrValueTypeName=" + attrValueTypeName
                  + "\n]";
      }

    }

创建换肤关了类SkinManagerT

复制代码
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.util.Log;
import android.view.View;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class SkinManagerT {
    public static final String TAG = "SkinManagerT";
    public volatile static SkinManagerT instance;
    private Context mContext;
    private Resources currentResources;
    private Resources themeResources;
    private final ConcurrentHashMap<View, HashMap<String, SkinAttrT>> mSkinAttrMap = new ConcurrentHashMap<>();

    public void init(Context context) {
        mContext = context;
        currentResources = mContext.getResources();
        //获取换肤资源Resource
        themeResources = getThemeResources(mContext);
    }

    private SkinManagerT() {

    }

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

    //将View保存到被监听的view列表中,使得在换肤时能够及时被更新
    public void saveSkinView(View view, HashMap<String, SkinAttrT> viewAttrs) {
        if (view == null || viewAttrs == null || viewAttrs.size() == 0) {
            return;
        }
        HashMap<String, SkinAttrT> originalSkinAttr = mSkinAttrMap.get(view);
        if (originalSkinAttr != null && originalSkinAttr.size() > 0) {
            originalSkinAttr.putAll(viewAttrs);
            mSkinAttrMap.put(view, originalSkinAttr);
        } else {
            mSkinAttrMap.put(view, viewAttrs);
        }
    }

    public Resources getDefaultResource() {
        return mContext.getResources();
    }

    public void restoreToDefaultSkin() {
        //换肤的时候切换resource
        currentResources = getDefaultResource();
        notifySkinChanged();
    }

    public void restoreToThemeSkin() {
        //换肤的时候切换resource
        currentResources = themeResources;
        notifySkinChanged();
    }


    /**
     * 遍历部署资源文件,将对应的资源部署到对应View上
     * 更换皮肤时,通知view更换资源
     */
    private void notifySkinChanged() {
        View view;
        HashMap<String, SkinAttrT> viewAttrs;
        Iterator<Map.Entry<View, HashMap<String, SkinAttrT>>> iter = mSkinAttrMap.entrySet().iterator();

        while (iter.hasNext()) {
            Map.Entry<View, HashMap<String, SkinAttrT>> entry = iter.next();
            view = entry.getKey();
            viewAttrs = entry.getValue();
            if (view != null) {
                deployViewSkinAttrs(view, viewAttrs);
            }
        }
        Log.d(TAG, "notifySkinChanged skinSize " + mSkinAttrMap.size());
    }

    public void deployViewSkinAttrs(View view, HashMap<String, SkinAttrT> viewAttrs) {

        if (view == null || viewAttrs == null || viewAttrs.size() == 0) {
            return;
        }
        Iterator<Map.Entry<String, SkinAttrT>> iter = viewAttrs.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry<String, SkinAttrT> entry = iter.next();
            SkinAttrT attr = entry.getValue();
            //判断当前保存的资源文件设置到哪个方法上的
            if (attr.attrName.equals(SkinConstantT.BACKGROUND)) {
                //判断保存的支援文件是哪个类型
                if (attr.attrValueTypeName.equals(SkinConstantT.COLOR)) {
                    //判断当前是说你哪个资源主题模式
                    if (currentResources == getDefaultResource()) {
                        //如果是默认,直接部署上去
                        view.setBackgroundColor(
                                getDefaultResource().getColor(attr.attrValueRefId, null)
                        );
                    } else {
                        //如果是主题资源,通过对应方法部署上去
                        view.setBackgroundColor(getThemeResourcesColorResId(attr, currentResources));
                    }
                }
            }
        }
    }

    public int getThemeResourcesColorResId(SkinAttrT viewAttrs, Resources resources) {

        //通过资源名称,获取到资源ID
        int newResId = resources.getIdentifier(
                viewAttrs.attrValueRefName,// 资源名
                viewAttrs.attrValueTypeName, // 资源类型
                "com.kx.skin" // 应用包名
        );
        Log.d(TAG, "getThemeResourcesColor newResId " + newResId);
        //通过资源ID,获取到对应资源的值
        int newColorResId = resources.getColor(newResId, null);
        Log.d(TAG, "getThemeResourcesColor newColorResId " + newColorResId);
        return newColorResId;
    }

    public Resources getThemeResources(Context context) {

        try {

            //通过反射创建AssetManager
//            AssetManager assetManager = AssetManager.class.newInstance();
//            Method add = assetManager.getClass().getDeclaredMethod("addAssetPath", String.class);
            //通过反射加载路径
//            int cookie = (int) add.invoke(assetManager, "/sdcard/skin/skin.skin");

            //将assets中的文件拷贝到自己私有的文件中
            File skinFile = copyAssetToFiles(context,
                    "skin-debug.apk",        // assets 下的相对路径
                    "skin-debug.apk");            // 目标文件名

            boolean exists = skinFile.exists();   // true 表示存在
            Log.e(TAG, "加载文件 exists " + exists + " getAbsolutePath " + skinFile.getAbsolutePath());
            // 创建资源Resources
            AssetManager assetManager = new AssetManager();
            int cookie = assetManager.addAssetPath(skinFile.getAbsolutePath());
            if (cookie == 0) {
                Log.e(TAG, "加载失败,路径无效或权限不足");
            }
            Resources oldRes = context.getResources();
            Resources newRes = new Resources(assetManager,
                    oldRes.getDisplayMetrics(),
                    oldRes.getConfiguration());

            return newRes;
        } catch (Throwable e) {
            e.printStackTrace();
            Log.d(TAG, "Throwable " + e);
        }

        return null;
    }

    public File copyAssetToFiles(Context ctx, String assetPath, String fileName) throws IOException {
        File outFile = new File(ctx.getFilesDir(), fileName);
        try (InputStream in = ctx.getAssets().open(assetPath);
             OutputStream out = Files.newOutputStream(outFile.toPath())) {
            byte[] buf = new byte[8192];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
        }
        return outFile;
    }
}

使用

复制代码
        setContentView(SkinLayoutInflaterT.from(this).inflate(R.layout.activity_theme, null));

SkinManagerT.getInstance().restoreToThemeSkin();

这里只是简单的把代码逻辑走了一遍,后面需要更多适配工作,现在换肤逻辑走完了