banner
NEWS LETTER

打造自己的换肤框架

Scroll down

换肤要知道View的创建流程

而且皮肤包的包名要和原包名一致,资源文件名称一致,而资源内容不一致,颜色名称一致,颜色不一致。

创建皮肤资源类SkinResource

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class SkinResource {
private Resources mSkinResources;
private String mPackageName;

public SkinResource(Context context, String skinPath) {
try {
Resources resources = context.getResources();
AssetManager assetManager = AssetManager.class.newInstance();
//通过反射设置资源
@SuppressLint("DiscouragedPrivateApi") Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
method.setAccessible(true);
method.invoke(assetManager, skinPath);
mSkinResources = new Resources(assetManager, resources.getDisplayMetrics(), resources.getConfiguration());
mPackageName = context.getPackageManager()
.getPackageArchiveInfo(skinPath, PackageManager.GET_ACTIVITIES).applicationInfo.packageName;
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 通过名字获取图片
*
* @param resName String
* @return Drawable
*/
@SuppressLint("UseCompatLoadingForDrawables")
public Drawable getDrawableByName(String resName) throws Resources.NotFoundException {
int resId = mSkinResources.getIdentifier(resName, "drawable", mPackageName);
if (resId == 0) {
return null;
}
return mSkinResources.getDrawable(resId);
}

/**
* 通过名字获取颜色
*
* @param resName String
* @return ColorStateList
*/
@SuppressLint("UseCompatLoadingForColorStateLists")
public ColorStateList getColorByName(String resName) throws Resources.NotFoundException {
int resId = mSkinResources.getIdentifier(resName, "color", mPackageName);
if (resId == 0) {
return null;
}
return mSkinResources.getColorStateList(resId);

}

public int getColorName(String name) throws Resources.NotFoundException {
int resId = mSkinResources.getIdentifier(name, "color", mPackageName);
if (resId == 0) {
return 0;
}
return mSkinResources.getColor(resId);
}
}

创建皮肤资源类型枚举类SkinType

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public enum SkinType {
/**
* 字体颜色
*/
TEXT_COLOR("textColor") {
@Override
public void skin(View view, String resName) {
SkinResource skinResource = getSkinResource();
ColorStateList colorByName = skinResource.getColorByName(resName);
if (null == colorByName) {
return;
}
TextView textView = (TextView) view;
textView.setTextColor(colorByName.getDefaultColor());
}
},
/**
* 背景色
*/
BACKGROUND("background") {
@Override
public void skin(View view, String resName) {
//背景可能是图片或者颜色
SkinResource skinResource = getSkinResource();
Drawable drawableByName = skinResource.getDrawableByName(resName);
if (null != drawableByName) {
view.setBackgroundDrawable(drawableByName);
return;
}
ColorStateList colorByName = skinResource.getColorByName(resName);
view.setBackgroundColor(colorByName.getDefaultColor());
}
},
/**
* 资源路径
*/
SRC("src") {
@Override
public void skin(View view, String resName) {
SkinResource skinResource = getSkinResource();
Drawable drawableByName = skinResource.getDrawableByName(resName);
if (null != drawableByName) {
ImageView imageView = (ImageView) view;
imageView.setImageDrawable(drawableByName);
}
}
};

public SkinResource getSkinResource() {
return SkinManager.getInstance().getSkinResource();
}

public String getResName() {
return mResName;
}

;
/**
* 会根据名称调对应的方法
*/
private final String mResName;

SkinType(String resName) {
this.mResName = resName;
}

public abstract void skin(View view, String resName);
}

创建资源类SkinAttr

1
2
3
4
5
6
7
8
9
10
11
12
13
public class SkinAttr {
private final String mResName;
private final SkinType mSkinType;

public SkinAttr(String resName, SkinType skinType) {
this.mResName = resName;
this.mSkinType = skinType;
}

public void skin(View view) {
mSkinType.skin(view, mResName);
}
}

创建皮肤视图SkinView

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SkinView {
private final View mView;
private final List<SkinAttr> mAttrs;

public SkinView(View view, List<SkinAttr> skinAttrs) {
this.mView = view;
this.mAttrs = skinAttrs;
}

public void skin() {
for (SkinAttr attr : mAttrs) {
attr.skin(mView);
}
}
}

创建皮肤变化监听接口ISkinChangeListener

1
2
3
4
5
6
7
8
public interface ISkinChangeListener {
/**
* 监听皮肤变化
*
* @param skinResource SkinResource
*/
void changeSkin(SkinResource skinResource);
}

创建状态码类SkinConfig

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class SkinConfig {
/**
* sp名称
*/
public static final String SKIN_INFO_NAME = "skin_info";
/**
* 皮肤文件的路径
*/
public static final String SKIN_PATH_NAME = "skin_path";
/**
* 换肤成功
*/
public static final int SKIN_CHANGE_SUCCESS = 1;
/**
* 什么都不做
*/
public static final int SKIN_CHANGE_NOTHING = -1;
/**
* 皮肤文件不存在
*/
public static final int SKIN_FILE_NO_EXISTS = -2;
/**
* 皮肤文件有错误
*/
public static final int SKIN_FILE_ERROR = -3;
}

使用sp创建皮肤状态管理SkinPreUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class SkinPreUtils {
private static SkinPreUtils mInstance;
private final Context mContext;

private SkinPreUtils(Context context) {
this.mContext = context.getApplicationContext();
}

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

/**
* 保存当前皮肤路径
*
* @param path String
*/
public void saveSkinPath(String path) {
mContext.getSharedPreferences(SkinConfig.SKIN_INFO_NAME, Context.MODE_PRIVATE)
.edit().putString(SkinConfig.SKIN_PATH_NAME, path).apply();
}

/**
* 获取当前皮肤路径
*/
public String getSkinPath() {
return mContext.getSharedPreferences(SkinConfig.SKIN_INFO_NAME, Context.MODE_PRIVATE)
.getString(SkinConfig.SKIN_PATH_NAME, "");
}

public void clearSkinInfo() {
saveSkinPath("");
}
}

复制官方AppCompatViewInflater,创建自己的SkinAppCompatViewInflater

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
public class SkinAppCompatViewInflater {

private static final Class<?>[] sConstructorSignature = new Class<?>[]{
Context.class, AttributeSet.class};
private static final int[] sOnClickAttrs = new int[]{android.R.attr.onClick};

private static final String[] sClassPrefixList = {
"android.widget.",
"android.view.",
"android.webkit."
};

private static final String LOG_TAG = "AppCompatViewInflater";

private static final SimpleArrayMap<String, Constructor<? extends View>> S_CONSTRUCTOR_MAP =
new SimpleArrayMap<>();

private final Object[] mConstructorArgs = new Object[2];

public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme) {
final Context originalContext = context;
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}

View view = null;
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
case "Spinner":
view = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case "ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case "RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckedTextView":
view = createCheckedTextView(context, attrs);
verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
view = createAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
view = createMultiAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "RatingBar":
view = createRatingBar(context, attrs);
verifyNotNull(view, name);
break;
case "SeekBar":
view = createSeekBar(context, attrs);
verifyNotNull(view, name);
break;
case "ToggleButton":
view = createToggleButton(context, attrs);
verifyNotNull(view, name);
break;
default:
view = createView(context, name, attrs);
}
if (view == null) {
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
checkOnClickListener(view, attrs);
}

return view;
}

@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}

@NonNull
protected AppCompatImageView createImageView(Context context, AttributeSet attrs) {
return new AppCompatImageView(context, attrs);
}

@NonNull
protected AppCompatButton createButton(Context context, AttributeSet attrs) {
return new AppCompatButton(context, attrs);
}

@NonNull
protected AppCompatEditText createEditText(Context context, AttributeSet attrs) {
return new AppCompatEditText(context, attrs);
}

@NonNull
protected AppCompatSpinner createSpinner(Context context, AttributeSet attrs) {
return new AppCompatSpinner(context, attrs);
}

@NonNull
protected AppCompatImageButton createImageButton(Context context, AttributeSet attrs) {
return new AppCompatImageButton(context, attrs);
}

@NonNull
protected AppCompatCheckBox createCheckBox(Context context, AttributeSet attrs) {
return new AppCompatCheckBox(context, attrs);
}

@NonNull
protected AppCompatRadioButton createRadioButton(Context context, AttributeSet attrs) {
return new AppCompatRadioButton(context, attrs);
}

@NonNull
protected AppCompatCheckedTextView createCheckedTextView(Context context, AttributeSet attrs) {
return new AppCompatCheckedTextView(context, attrs);
}

@NonNull
protected AppCompatAutoCompleteTextView createAutoCompleteTextView(Context context,
AttributeSet attrs) {
return new AppCompatAutoCompleteTextView(context, attrs);
}

@NonNull
protected AppCompatMultiAutoCompleteTextView createMultiAutoCompleteTextView(Context context,
AttributeSet attrs) {
return new AppCompatMultiAutoCompleteTextView(context, attrs);
}

@NonNull
protected AppCompatRatingBar createRatingBar(Context context, AttributeSet attrs) {
return new AppCompatRatingBar(context, attrs);
}

@NonNull
protected AppCompatSeekBar createSeekBar(Context context, AttributeSet attrs) {
return new AppCompatSeekBar(context, attrs);
}

@NonNull
protected AppCompatToggleButton createToggleButton(Context context, AttributeSet attrs) {
return new AppCompatToggleButton(context, attrs);
}

private void verifyNotNull(View view, String name) {
if (view == null) {
throw new IllegalStateException(this.getClass().getName()
+ " asked to inflate view for <" + name + ">, but returned null");
}
}

@Nullable
protected View createView(Context context, String name, AttributeSet attrs) {
return null;
}

private View createViewFromTag(Context context, String name, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}

try {
mConstructorArgs[0] = context;
mConstructorArgs[1] = attrs;

if (-1 == name.indexOf('.')) {
for (String s : sClassPrefixList) {
final View view = createViewByPrefix(context, name, s);
if (view != null) {
return view;
}
}
return null;
} else {
return createViewByPrefix(context, name, null);
}
} catch (Exception e) {
// We do not want to catch these, lets return null and let the actual LayoutInflater
// try
return null;
} finally {
// Don't retain references on context.
mConstructorArgs[0] = null;
mConstructorArgs[1] = null;
}
}

/**
* android:onClick doesn't handle views with a ContextWrapper context. This method
* backports new framework functionality to traverse the Context wrappers to find a
* suitable target.
*/
private void checkOnClickListener(View view, AttributeSet attrs) {
final Context context = view.getContext();

if (!(context instanceof ContextWrapper) ||
(Build.VERSION.SDK_INT >= 15 && !ViewCompat.hasOnClickListeners(view))) {
return;
}

final TypedArray a = context.obtainStyledAttributes(attrs, sOnClickAttrs);
final String handlerName = a.getString(0);
if (handlerName != null) {
view.setOnClickListener(new DeclaredOnClickListener(view, handlerName));
}
a.recycle();
}

private View createViewByPrefix(Context context, String name, String prefix)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = S_CONSTRUCTOR_MAP.get(name);

try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
Class<? extends View> clazz = Class.forName(
prefix != null ? (prefix + name) : name,
false,
context.getClassLoader()).asSubclass(View.class);

constructor = clazz.getConstructor(sConstructorSignature);
S_CONSTRUCTOR_MAP.put(name, constructor);
}
constructor.setAccessible(true);
return constructor.newInstance(mConstructorArgs);
} catch (Exception e) {
// We do not want to catch these, lets return null and let the actual LayoutInflater
// try
return null;
}
}

/**
* Allows us to emulate the {@code android:theme} attribute for devices before L.
*/
private static Context themifyContext(Context context, AttributeSet attrs,
boolean useAndroidTheme, boolean useAppTheme) {
final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.View, 0, 0);
int themeId = 0;
if (useAndroidTheme) {
// First try reading android:theme if enabled
themeId = a.getResourceId(R.styleable.View_android_theme, 0);
}
if (useAppTheme && themeId == 0) {
// ...if that didn't work, try reading app:theme (for legacy reasons) if enabled
themeId = a.getResourceId(R.styleable.View_theme, 0);

if (themeId != 0) {
Log.i(LOG_TAG, "app:theme is now deprecated. "
+ "Please move to using android:theme instead.");
}
}
a.recycle();

if (themeId != 0 && (!(context instanceof ContextThemeWrapper)
|| ((ContextThemeWrapper) context).getThemeResId() != themeId)) {
// If the context isn't a ContextThemeWrapper, or it is but does not have
// the same theme as we need, wrap it in a new wrapper
context = new ContextThemeWrapper(context, themeId);
}
return context;
}

/**
* An implementation of OnClickListener that attempts to lazily load a
* named click handling method from a parent or ancestor context.
*/
private static class DeclaredOnClickListener implements View.OnClickListener {
private final View mHostView;
private final String mMethodName;

private Method mResolvedMethod;
private Context mResolvedContext;

public DeclaredOnClickListener(@NonNull View hostView, @NonNull String methodName) {
mHostView = hostView;
mMethodName = methodName;
}

@Override
public void onClick(@NonNull View v) {
if (mResolvedMethod == null) {
resolveMethod(mHostView.getContext());
}

try {
mResolvedMethod.invoke(mResolvedContext, v);
} catch (IllegalAccessException e) {
throw new IllegalStateException(
"Could not execute non-public method for android:onClick", e);
} catch (InvocationTargetException e) {
throw new IllegalStateException(
"Could not execute method for android:onClick", e);
}
}

private void resolveMethod(@Nullable Context context) {
while (context != null) {
try {
if (!context.isRestricted()) {
final Method method = context.getClass().getMethod(mMethodName, View.class);
if (method != null) {
mResolvedMethod = method;
mResolvedContext = context;
return;
}
}
} catch (NoSuchMethodException e) {
// Failed to find method, keep searching up the hierarchy.
}

if (context instanceof ContextWrapper) {
context = ((ContextWrapper) context).getBaseContext();
} else {
// Can't search up the hierarchy, null out and fail.
context = null;
}
}

final int id = mHostView.getId();
final String idText = id == View.NO_ID ? "" : " with id '"
+ mHostView.getContext().getResources().getResourceEntryName(id) + "'";
throw new IllegalStateException("Could not find method " + mMethodName
+ "(View) in a parent or ancestor Context for android:onClick "
+ "attribute defined on view " + mHostView.getClass() + idText);
}
}
}

创建资源支持类SkinAttrSupport

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class SkinAttrSupport {
public static List<SkinAttr> getSkinView(Context context, AttributeSet attrs) {
//textColor,background,src
List<SkinAttr> skinAttrs = new ArrayList<>();
int attrLength = attrs.getAttributeCount();
for (int i = 0; i < attrLength; i++) {
//获取名称
String attrName = attrs.getAttributeName(i);
String attrValue = attrs.getAttributeValue(i);
//只获取重要的
SkinType skinType = getSkinType(attrName);
if (skinType != null) {
//资源名称,目前只有value,是一个int类型
String resName = getResName(context, attrValue);
if (TextUtils.isEmpty(resName)) {
continue;
}
SkinAttr skinAttr = new SkinAttr(resName, skinType);
skinAttrs.add(skinAttr);
}
}
return skinAttrs;
}

/**
* 获取资源的名称
*
* @param context Context
* @param attrValue String
* @return String
*/
private static String getResName(Context context, String attrValue) {
if (attrValue.startsWith("@")) {
attrValue = attrValue.substring(1);
int resId = Integer.parseInt(attrValue);
return context.getResources().getResourceEntryName(resId);
}
return null;
}

/**
* 通过名称获取skinType
*
* @param attrName String
* @return SkinType
*/
private static SkinType getSkinType(String attrName) {
SkinType[] skinTypes = SkinType.values();
for (SkinType skinType : skinTypes) {
if (skinType.getResName().equals(attrName)) {
return skinType;
}
}
return null;
}
}

创建皮肤管理类SkinManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
public class SkinManager {
private static final SkinManager INSTANCE;
private Context mContext;
private final Map<ISkinChangeListener, List<SkinView>> mSkinViews = new ArrayMap<>();
private SkinResource mSkinResource;
static {
INSTANCE = new SkinManager();
}

public static SkinManager getInstance() {
return INSTANCE;
}

public void init(Context context) {
mContext = context.getApplicationContext();
//每次打开应用都会到此,防止皮肤删除后果,做一些处理
String currentSkinPath = SkinPreUtils.getInstance(context).getSkinPath();
File file = new File(currentSkinPath);
if (!file.exists()) {
//不存在,就清空皮肤
SkinPreUtils.getInstance(context).clearSkinInfo();
return;
}
//最好获取一下包名
String mPackageName = context.getPackageManager()
.getPackageArchiveInfo(currentSkinPath, PackageManager.GET_ACTIVITIES).applicationInfo.packageName;
if (TextUtils.isEmpty(mPackageName)) {
//不存在,就清空皮肤
SkinPreUtils.getInstance(context).clearSkinInfo();
return;
}
//最好校验一下签名
//做一些初始化的工作
mSkinResource = new SkinResource(mContext, currentSkinPath);
}

/**
* 加载皮肤
*
* @param skinPath String
* @return int
*/
public int loadSkin(String skinPath) {
File file = new File(skinPath);
if (!file.exists()) {
return SkinConfig.SKIN_FILE_NO_EXISTS;
}
//最好获取一下包名
String mPackageName = mContext.getPackageManager()
.getPackageArchiveInfo(skinPath, PackageManager.GET_ACTIVITIES).applicationInfo.packageName;
if (TextUtils.isEmpty(mPackageName)) {
return SkinConfig.SKIN_FILE_ERROR;
}
//当前皮肤一样,不需要换
String currentSkinPath = SkinPreUtils.getInstance(mContext).getSkinPath();
if (skinPath.equals(currentSkinPath)) {
return SkinConfig.SKIN_CHANGE_NOTHING;
}
//校验签名,增量更新使用
//初始化资源管理器
mSkinResource = new SkinResource(mContext, skinPath);
//改变皮肤
changeSkin();
//保存皮肤状态
saveSkinStatus(skinPath);
return 0;
}

/**
* 改变皮肤
*/
private void changeSkin() {
Set<ISkinChangeListener> keys = mSkinViews.keySet();
for (ISkinChangeListener key : keys) {
List<SkinView> skinViews = mSkinViews.get(key);
for (SkinView skinView : skinViews) {
skinView.skin();
}
//通知activity
key.changeSkin(mSkinResource);
}
}

/**
* 保存皮肤
*
* @param skinPath String
*/
private void saveSkinStatus(String skinPath) {
SkinPreUtils.getInstance(mContext).saveSkinPath(skinPath);
}

/**
* 恢复默认
*
* @return int
*/
public int restoreDefault() {
//判断当前有没有皮肤
String currentSkinPath = SkinPreUtils.getInstance(mContext).getSkinPath();
if (TextUtils.isEmpty(currentSkinPath)) {
return SkinConfig.SKIN_CHANGE_NOTHING;
}
//初始化资源管理器
String nowSkinPath = mContext.getPackageResourcePath();
mSkinResource = new SkinResource(mContext, nowSkinPath);
//改变皮肤
changeSkin();
//把皮肤信息清空
SkinPreUtils.getInstance(mContext).clearSkinInfo();
return SkinConfig.SKIN_CHANGE_SUCCESS;
}

/**
* 获取skinView 通过ISkinChangeListener
*
* @param listener ISkinChangeListener
* @return List<SkinView>
*/
public List<SkinView> getSkinViews(ISkinChangeListener listener) {
return mSkinViews.get(listener);
}


/**
* 获取当前皮肤的一个资源管理
*
* @return SkinResource
*/
public SkinResource getSkinResource() {
return mSkinResource;
}

/**
* 要不要换肤
*
* @param skinView SkinView
*/
public void checkChangeSkin(SkinView skinView) {
//如果当前有皮肤
String currentSkinPath = SkinPreUtils.getInstance(mContext).getSkinPath();
if (!TextUtils.isEmpty(currentSkinPath)) {
skinView.skin();
}
}

/**
* 注册
*
* @param listener ISkinChangeListener
* @param skinViews List<SkinView>
*/
public void register(ISkinChangeListener listener, List<SkinView> skinViews) {
mSkinViews.put(listener, skinViews);
}

/**
* 取消注册,防止内存泄露
*
* @param listener ISkinChangeListener
*/
public void unRegister(ISkinChangeListener listener) {
mSkinViews.remove(listener);
}
}

创建BaseSkinActivity继承BaseActivity实现LayoutInflater.Factory2, ISkinChangeListener接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public abstract class BaseSkinActivity extends BaseActivity implements LayoutInflater.Factory2, ISkinChangeListener {
private SkinAppCompatViewInflater mAppCompatViewInflater;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
LayoutInflater layoutInflater = LayoutInflater.from(this);
LayoutInflaterCompat.setFactory2(layoutInflater, this);
super.onCreate(savedInstanceState);
}

@Nullable
@Override
public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) {
View view = createView(parent, name, context, attrs);
if (view != null) {
List<SkinAttr> skinAttrs = SkinAttrSupport.getSkinView(context, attrs);
SkinView skinView = new SkinView(view, skinAttrs);
//统一交给manager管理
managerSkinView(skinView);
//判断一下要不要换肤
SkinManager.getInstance().checkChangeSkin(skinView);
}
return view;
}

/**
* 统一管理SkinView
*
* @param skinView
*/
private void managerSkinView(SkinView skinView) {
List<SkinView> skinViews = SkinManager.getInstance().getSkinViews(this);
if (skinViews == null) {
skinViews = new ArrayList<>();
SkinManager.getInstance().register(this, skinViews);
}
skinViews.add(skinView);
}
@Override
public void changeSkin(SkinResource skinResource) {
//做一些第三方的改变
}

@Override
protected void onDestroy() {
//取消监听,防止内存泄露
SkinManager.getInstance().unRegister(this);
super.onDestroy();
}

private View createView(View parent, String name, Context context,AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
mAppCompatViewInflater = new SkinAppCompatViewInflater();
}
final boolean inheritContext = shouldInheritContext((ViewParent) parent);
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,true,true);
}


private boolean shouldInheritContext(ViewParent parent) {
if (parent == null) {
// The initial parent is null so just return false
return false;
}
final View windowDecor = getWindow().getDecorView();
while (true) {
if (parent == null) {
// Bingo. We've hit a view which has a null parent before being terminated from
// the loop. This is (most probably) because it's the root view in an inflation
// call, therefore we should inherit. This works as the inflated layout is only
// added to the hierarchy at the end of the inflate() call.
return true;
} else if (parent == windowDecor || !(parent instanceof View)
|| ViewCompat.isAttachedToWindow((View) parent)) {
// We have either hit the window's decor view, a parent which isn't a View
// (i.e. ViewRootImpl), or an attached view, so we know that the original parent
// is currently added to the view hierarchy. This means that it has not be
// inflated in the current inflate() call and we should not inherit the context.
return false;
}
parent = parent.getParent();
}
}
}

使用方式

首先在Application中初始化
1
SkinManager.getInstance().init(this);
在需要换肤的地方加载皮肤
1
SkinManager.getInstance().loadSkin(path)
恢复默认皮肤
1
SkinManager.getInstance().restoreDefault();
其他文章
目录导航 置顶
  1. 1. 换肤要知道View的创建流程
  2. 2. 创建皮肤资源类SkinResource
  3. 3. 创建皮肤资源类型枚举类SkinType
  4. 4. 创建资源类SkinAttr
  5. 5. 创建皮肤视图SkinView
  6. 6. 创建皮肤变化监听接口ISkinChangeListener
  7. 7. 创建状态码类SkinConfig
  8. 8. 使用sp创建皮肤状态管理SkinPreUtils
  9. 9. 复制官方AppCompatViewInflater,创建自己的SkinAppCompatViewInflater
  10. 10. 创建资源支持类SkinAttrSupport
  11. 11. 创建皮肤管理类SkinManager
  12. 12. 创建BaseSkinActivity继承BaseActivity实现LayoutInflater.Factory2, ISkinChangeListener接口
  13. 13. 使用方式
请输入关键词进行搜索