Skip to content

Commit

Permalink
支持在theme主题中设置的属性换肤
Browse files Browse the repository at this point in the history
  • Loading branch information
mxlei committed Aug 31, 2022
1 parent 3acb83a commit 642d6cd
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 46 deletions.
1 change: 1 addition & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 5 additions & 12 deletions app/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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="默认皮肤" />

<Button
android:id="@+id/btn_gray_skin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/bg_button"
android:text="儒雅灰"
android:textColor="@color/buttonTextColor" />
android:text="儒雅灰" />

<Button
android:id="@+id/btn_red_skin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/bg_button"
android:text="高贵红"
android:textColor="@color/buttonTextColor" />
android:text="高贵红" />

<Button
android:id="@+id/btn_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:background="@drawable/bg_button"
android:text="Fragment测试"
android:textColor="@color/buttonTextColor" />
android:text="Fragment测试" />

<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
Expand Down
7 changes: 4 additions & 3 deletions app/src/main/res/values/themes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
<item name="android:windowBackground">@color/colorWindowBackground</item>
<item name="android:buttonStyle">@style/ButtonStyle</item>
</style>

<style name="ButtonStyle" parent="TextAppearance.AppCompat.Button">
<item name="android:textColor">@color/black</item>
<item name="background">@drawable/bg_button</item>
<style name="ButtonStyle" parent="Widget.AppCompat.Button">
<item name="android:background">@drawable/bg_button</item>
<item name="android:textColor">@color/buttonTextColor</item>
</style>
</resources>
15 changes: 15 additions & 0 deletions skin-changer/src/main/java/com/github/jeffery/skin/SkinAttr.java
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
36 changes: 27 additions & 9 deletions skin-changer/src/main/java/com/github/jeffery/skin/SkinManger.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, Integer>[] 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<String> attrSet = new HashSet<>();

private SkinManger() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,36 @@

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
* @date 2022/8/28
*/
public class SkinViewCreateFactory2 implements LayoutInflater.Factory2 {

/**
* 在theme中定义view默认属性
* key为view的类型,value为属性集
*/
private final Map<Class<?>, List<SkinAttr>> skinAttrsOnTheme = new HashMap<>();

@Nullable
@Override
Expand Down Expand Up @@ -62,46 +76,147 @@ 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);
}
}
return view;
}

@NonNull
private List<SkinAttr> getDefaultSkinAttrList(View view, Class<?> clazz, @AttrRes int styleableAttrRes) {
List<SkinAttr> 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<String, Integer> 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<SkinAttr> 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<SkinAttr> attrList = new ArrayList<>();
// View的所有可换肤的属性集合,key为属性名称
Map<String, SkinAttr> skinAttrMap = new HashMap<>();
// theme中定义的控件属性
List<SkinAttr> 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<String, Integer> 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;
}
}

0 comments on commit 642d6cd

Please sign in to comment.