diff --git a/.idea/misc.xml b/.idea/misc.xml
index 5b6a587..9cc694b 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -11,6 +11,7 @@
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 59e39d4..c32d339 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -22,36 +22,29 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
- android:background="@drawable/bg_button"
- android:text="默认皮肤"
- android:textColor="@color/buttonTextColor" />
+ android:text="默认皮肤" />
+ android:text="儒雅灰" />
+ android:text="高贵红" />
+ android:text="Fragment测试" />
+
?attr/colorPrimaryVariant
- @color/colorWindowBackground
+ - @style/ButtonStyle
-
\ No newline at end of file
diff --git a/skin-changer/src/main/java/com/github/jeffery/skin/SkinAttr.java b/skin-changer/src/main/java/com/github/jeffery/skin/SkinAttr.java
index 13c56bf..2d675ce 100644
--- a/skin-changer/src/main/java/com/github/jeffery/skin/SkinAttr.java
+++ b/skin-changer/src/main/java/com/github/jeffery/skin/SkinAttr.java
@@ -5,10 +5,25 @@
* @date 2022/8/28
*/
public class SkinAttr {
+ /**
+ * 原app中的资源ID
+ */
private int originResId;
+ /**
+ * 皮肤app中的资源ID
+ */
private int skinResId;
+ /**
+ * 资源类型
+ */
private String skinResType;
+ /**
+ * 资源名称
+ */
private String skinResEntryName;
+ /**
+ * 属性名称
+ */
private String attrName;
public int getOriginResId() {
diff --git a/skin-changer/src/main/java/com/github/jeffery/skin/SkinManger.java b/skin-changer/src/main/java/com/github/jeffery/skin/SkinManger.java
index dfe2404..2cf8207 100644
--- a/skin-changer/src/main/java/com/github/jeffery/skin/SkinManger.java
+++ b/skin-changer/src/main/java/com/github/jeffery/skin/SkinManger.java
@@ -19,6 +19,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
+import androidx.core.util.Pair;
import androidx.core.view.LayoutInflaterCompat;
import java.io.File;
@@ -47,16 +48,33 @@ public class SkinManger extends SkinActivityLifecycleCallback {
private static final String RES_TYPE_NAME_COLOR = "color";
private static final String RES_TYPE_NAME_DRAWABLE = "drawable";
- private static final String ATTR_NAME_SRC = "src";
- private static final String ATTR_NAME_TEXT_COLOR = "textColor";
- private static final String ATTR_NAME_BACKGROUND = "background";
- private static final String ATTR_NAME_DRAWABLE_START = "drawableStart";
- private static final String ATTR_NAME_DRAWABLE_LEFT = "drawableLeft";
- private static final String ATTR_NAME_DRAWABLE_TOP = "drawableTop";
- private static final String ATTR_NAME_DRAWABLE_RIGHT = "drawableRight";
- private static final String ATTR_NAME_DRAWABLE_END = "drawableEnd";
- private static final String ATTR_NAME_DRAWABLE_BOTTOM = "drawableBottom";
+ public static final String ATTR_NAME_SRC = "src";
+ public static final String ATTR_NAME_TEXT_COLOR = "textColor";
+ public static final String ATTR_NAME_BACKGROUND = "background";
+ public static final String ATTR_NAME_DRAWABLE_START = "drawableStart";
+ public static final String ATTR_NAME_DRAWABLE_LEFT = "drawableLeft";
+ public static final String ATTR_NAME_DRAWABLE_TOP = "drawableTop";
+ public static final String ATTR_NAME_DRAWABLE_RIGHT = "drawableRight";
+ public static final String ATTR_NAME_DRAWABLE_END = "drawableEnd";
+ public static final String ATTR_NAME_DRAWABLE_BOTTOM = "drawableBottom";
+ /**
+ * 布局属性名和属性ID的一一对应
+ */
+ public static final Pair[] ATTR_STYLE_RESOURCES = new Pair[]{
+ new Pair(ATTR_NAME_BACKGROUND, android.R.attr.background),
+ new Pair(ATTR_NAME_TEXT_COLOR, android.R.attr.textColor),
+ new Pair(ATTR_NAME_SRC, android.R.attr.src),
+ new Pair(ATTR_NAME_DRAWABLE_LEFT, android.R.attr.drawableLeft),
+ new Pair(ATTR_NAME_DRAWABLE_START, android.R.attr.drawableStart),
+ new Pair(ATTR_NAME_DRAWABLE_TOP, android.R.attr.drawableTop),
+ new Pair(ATTR_NAME_DRAWABLE_RIGHT, android.R.attr.drawableRight),
+ new Pair(ATTR_NAME_DRAWABLE_END, android.R.attr.drawableEnd),
+ new Pair(ATTR_NAME_DRAWABLE_BOTTOM, android.R.attr.drawableBottom),
+ };
+ /**
+ * 所有支持换肤的属性名
+ */
private final Set attrSet = new HashSet<>();
private SkinManger() {
diff --git a/skin-changer/src/main/java/com/github/jeffery/skin/SkinViewCreateFactory2.java b/skin-changer/src/main/java/com/github/jeffery/skin/SkinViewCreateFactory2.java
index 0acfd4b..5dc9180 100644
--- a/skin-changer/src/main/java/com/github/jeffery/skin/SkinViewCreateFactory2.java
+++ b/skin-changer/src/main/java/com/github/jeffery/skin/SkinViewCreateFactory2.java
@@ -2,15 +2,24 @@
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.util.AttributeSet;
+import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import androidx.annotation.AttrRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.StyleRes;
+import androidx.core.util.Pair;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* @author mxlei
@@ -18,6 +27,11 @@
*/
public class SkinViewCreateFactory2 implements LayoutInflater.Factory2 {
+ /**
+ * 在theme中定义view默认属性
+ * key为view的类型,value为属性集
+ */
+ private final Map, List> skinAttrsOnTheme = new HashMap<>();
@Nullable
@Override
@@ -62,7 +76,7 @@ public View onCreateView(@NonNull String name, @NonNull Context context, @NonNul
e.printStackTrace();
}
if (ac != null && view != null) {
- SkinView skinView = wrapSkinView(view, attrs);
+ SkinView skinView = toSkinView(view, attrs);
if (skinView != null) {
ac.getSkinViewList().add(skinView);
}
@@ -70,38 +84,139 @@ public View onCreateView(@NonNull String name, @NonNull Context context, @NonNul
return view;
}
+ @NonNull
+ private List getDefaultSkinAttrList(View view, Class> clazz, @AttrRes int styleableAttrRes) {
+ List cachedAttrs = skinAttrsOnTheme.get(clazz);
+ if (cachedAttrs == null) {
+ cachedAttrs = new ArrayList<>();
+ TypedArray ta = view.getContext().obtainStyledAttributes(new int[]{styleableAttrRes});
+ int resId = ta.getResourceId(0, 0);
+ if (resId != 0) {
+ for (Pair pair : SkinManger.ATTR_STYLE_RESOURCES) {
+ SkinAttr skinAttr = toSkinAttr(view.getContext(), pair.first, resId, pair.second);
+ if (skinAttr != null) {
+ cachedAttrs.add(skinAttr);
+ }
+ }
+ }
+ ta.recycle();
+ skinAttrsOnTheme.put(clazz, cachedAttrs);
+ }
+ return cachedAttrs;
+ }
- private SkinView wrapSkinView(@Nullable View view, @NonNull AttributeSet attrs) {
+ @Nullable
+ private List getDefaultSkinAttrList(View view) {
+ if (view instanceof Button) {
+ return getDefaultSkinAttrList(view, Button.class, android.R.attr.buttonStyle);
+ }
+ if (view instanceof TextView) {
+ return getDefaultSkinAttrList(view, TextView.class, android.R.attr.textStyle);
+ }
+ return null;
+ }
+
+ /**
+ * 转换view为可换肤的View
+ *
+ * @return 当view不支持换肤时返回null
+ */
+ private SkinView toSkinView(@Nullable View view, @NonNull AttributeSet attrs) {
int count = attrs.getAttributeCount();
if (view == null || count == 0) {
return null;
}
- Resources appResources = view.getContext().getResources();
- List attrList = new ArrayList<>();
+ // View的所有可换肤的属性集合,key为属性名称
+ Map skinAttrMap = new HashMap<>();
+ // theme中定义的控件属性
+ List defaultAttrs = getDefaultSkinAttrList(view);
+ if (defaultAttrs != null && !defaultAttrs.isEmpty()) {
+ for (SkinAttr skinAttr : defaultAttrs) {
+ skinAttrMap.put(skinAttr.getAttrName(), skinAttr);
+ }
+ }
for (int i = 0; i < count; i++) {
String attrName = attrs.getAttributeName(i);
String attrValue = attrs.getAttributeValue(i);
- if (SkinManger.getInstance().isSupportedAttribute(attrName)) {
- if (attrValue.startsWith("@")) {
- try {
- int resId = Integer.parseInt(attrValue.substring(1));
- String resName = appResources.getResourceName(resId);
- String resType = appResources.getResourceTypeName(resId);
- String resEntryName = appResources.getResourceEntryName(resId);
- SkinAttr skAttr = new SkinAttr();
- skAttr.setAttrName(attrName);
- skAttr.setSkinResEntryName(resEntryName);
- skAttr.setSkinResType(resType);
- skAttr.setOriginResId(resId);
- attrList.add(skAttr);
- } catch (Exception e) {
- e.printStackTrace();
+ if ("style".equals(attrName)) {
+ // 当布局中使用style定义控件的属性集时,解析属性集中的每个属性
+ int resId = Integer.parseInt(attrValue.substring(1));
+ for (Pair pair : SkinManger.ATTR_STYLE_RESOURCES) {
+ SkinAttr skinAttr = toSkinAttr(view.getContext(), pair.first, resId, pair.second);
+ if (skinAttr != null) {
+ skinAttrMap.put(skinAttr.getAttrName(), skinAttr);
}
}
- } else if (attrValue.startsWith("?")) {
- // todo
+ } else {
+ // 布局中的属性转换为皮肤属性
+ SkinAttr skinAttr = toSkinAttr(view.getContext(), attrName, attrValue);
+ if (skinAttr != null) {
+ skinAttrMap.put(skinAttr.getAttrName(), skinAttr);
+ }
}
}
- return attrList.isEmpty() ? null : new SkinView(view, attrList);
+ if (skinAttrMap.isEmpty()) {
+ return null;
+ } else {
+ return new SkinView(view, new ArrayList<>(skinAttrMap.values()));
+ }
+ }
+
+ /**
+ * 转换属性为皮肤属性
+ */
+ @Nullable
+ private SkinAttr toSkinAttr(Context context, String attrName, String attrValue) {
+ if (attrValue == null || attrName.length() < 2) {
+ return null;
+ }
+ Resources resources = context.getResources();
+ if (SkinManger.getInstance().isSupportedAttribute(attrName)) {
+ if (attrValue.startsWith("@")) {
+ // @后面是资源ID
+ try {
+ int resId = Integer.parseInt(attrValue.substring(1));
+ String resName = resources.getResourceName(resId);
+ String resType = resources.getResourceTypeName(resId);
+ String resEntryName = resources.getResourceEntryName(resId);
+ SkinAttr skAttr = new SkinAttr();
+ skAttr.setAttrName(attrName);
+ skAttr.setSkinResEntryName(resEntryName);
+ skAttr.setSkinResType(resType);
+ skAttr.setOriginResId(resId);
+ return skAttr;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ } else if (attrValue.startsWith("?")) {
+ // ?后面是另一个属性的ID,通过另一个属性来获取真正的资源ID
+ TypedArray ta = context.obtainStyledAttributes(new int[]{Integer.parseInt(attrValue.substring(1))});
+ int resId = ta.getResourceId(0, 0);
+ ta.recycle();
+ if (resId != 0) {
+ return toSkinAttr(context, attrName, "@" + resId);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 从定义style属性集中获取一个属性,转换为皮肤属性
+ */
+ @Nullable
+ private SkinAttr toSkinAttr(@NonNull Context context,
+ String attrName,
+ @StyleRes int styleRes,
+ @AttrRes int styleableAttr) {
+ SkinAttr skinAttr = null;
+ TypedValue typedValue = new TypedValue();
+ TypedArray ta = context.obtainStyledAttributes(styleRes, new int[]{styleableAttr});
+ if (ta.getValue(0, typedValue)) {
+ String attrValue = "@" + ta.getResourceId(0, 0);
+ skinAttr = toSkinAttr(context, attrName, attrValue);
+ }
+ ta.recycle();
+ return skinAttr;
}
}