Android-skin-support
https://github.com/ximsfei/Android-skin-support
1、皮肤切换触发点
设置页面选址设置夜间模式-应用内换肤
SettingsFragment.java
if (boolValue) {
SkinCompatManager.getInstance().loadSkin("night", null, SkinCompatManager.SKIN_LOADER_STRATEGY_BUILD_IN);
} else {
SkinCompatManager.getInstance().restoreDefaultTheme();
}
2、 SkinObservable 进行 notifyUpdateSkin
SkinCompatManager.java 通知皮肤更新
public AsyncTask loadSkin(String skinName, SkinLoaderListener listener, int strategy) {
SkinLoaderStrategy loaderStrategy = mStrategyMap.get(strategy);
if (loaderStrategy == null) {
return null;
}
return new SkinLoadTask(listener, loaderStrategy).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, skinName);
}
’
private class SkinLoadTask extends AsyncTask {
private final SkinLoaderListener mListener;
private final SkinLoaderStrategy mStrategy;
SkinLoadTask(@Nullable SkinLoaderListener listener, @NonNull SkinLoaderStrategy strategy) {
mListener = listener;
mStrategy = strategy;
}
@Override
protected void onPreExecute() {
if (mListener != null) {
mListener.onStart();
}
}
@Override
protected String doInBackground(String... params) {
synchronized (mLock) {
while (mLoading) {
try {
mLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mLoading = true;
}
try {
if (params.length == 1) {
String skinName = mStrategy.loadSkinInBackground(mAppContext, params[0]);
if (TextUtils.isEmpty(skinName)) {
SkinCompatResources.getInstance().reset(mStrategy);
}
return params[0];
}
} catch (Exception e) {
e.printStackTrace();
}
SkinCompatResources.getInstance().reset();
return null;
}
@Override
protected void onPostExecute(String skinName) {
synchronized (mLock) {
// skinName 为""时,恢复默认皮肤
if (skinName != null) {
SkinPreference.getInstance().setSkinName(skinName).setSkinStrategy(mStrategy.getType()).commitEditor();
notifyUpdateSkin();
if (mListener != null) {
mListener.onSuccess();
}
} else {
SkinPreference.getInstance().setSkinName("").setSkinStrategy(SKIN_LOADER_STRATEGY_NONE).commitEditor();
if (mListener != null) {
mListener.onFailed("皮肤资源获取失败");
}
}
mLoading = false;
mLock.notifyAll();
}
}
}
3、app 中Application App.java初始化,添加Observer
SkinCompatManager.withoutActivity(this)
.addStrategy(new CustomSDCardLoader()) // 自定义加载策略,指定SDCard路径
.addStrategy(new ZipSDCardLoader()) // 自定义加载策略,获取zip包中的资源
.addInflater(new SkinAppCompatViewInflater()) // 基础控件换肤
.addInflater(new SkinMaterialViewInflater())
private SkinActivityLifecycle(Application application) {
application.registerActivityLifecycleCallbacks(this);
installLayoutFactory(application);
SkinCompatManager.getInstance().addObserver(getObserver(application));
}
4、SkinActivityLifecycle updateSkinForce 进行皮肤刷新
SkinActivityLifecycle.java getSkinDelegate
private class LazySkinObserver implements SkinObserver {
private final Context mContext;
private boolean mMarkNeedUpdate = false;
LazySkinObserver(Context context) {
mContext = context;
}
@Override
public void updateSkin(SkinObservable observable, Object o) {
// 当前Activity,或者非Activity,立即刷新,否则延迟到下次onResume方法中刷新。
if (mCurActivityRef == null
|| mContext == mCurActivityRef.get()
|| !(mContext instanceof Activity)) {
updateSkinForce();
} else {
mMarkNeedUpdate = true;
}
}
void updateSkinIfNeeded() {
if (mMarkNeedUpdate) {
updateSkinForce();
}
}
void updateSkinForce() {
if (Slog.DEBUG) {
Slog.i(TAG, "Context: " + mContext + " updateSkinForce");
}
if (mContext == null) {
return;
}
if (mContext instanceof Activity && isContextSkinEnable(mContext)) {
updateWindowBackground((Activity) mContext);
}
getSkinDelegate(mContext).applySkin();
if (mContext instanceof SkinCompatSupportable) {
((SkinCompatSupportable) mContext).applySkin();
}
mMarkNeedUpdate = false;
}
}
5、SkinCompatDelegate .java create
private SkinCompatDelegate getSkinDelegate(Context context) {
if (mSkinDelegateMap == null) {
mSkinDelegateMap = new WeakHashMap<>();
}
SkinCompatDelegate mSkinDelegate = mSkinDelegateMap.get(context);
if (mSkinDelegate == null) {
mSkinDelegate = SkinCompatDelegate.create(context);
mSkinDelegateMap.put(context, mSkinDelegate);
}
return mSkinDelegate;
}
6、SkinCompatViewInflater.java 进行view皮肤切换 createViewFromInflater
public final View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) {
View view = createViewFromHackInflater(context, name, attrs);
if (view == null) {
view = createViewFromInflater(context, name, attrs);
}
if (view == null) {
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check it's android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
private View createViewFromInflater(Context context, String name, AttributeSet attrs) {
View view = null;
for (SkinLayoutInflater inflater : SkinCompatManager.getInstance().getInflaters()) {
view = inflater.createView(context, name, attrs);
if (view == null) {
continue;
} else {
break;
}
}
return view;
}
7、app 中Application
App.java 初始化 SkinAppCompatViewInflater
SkinCompatManager.withoutActivity(this)
.addStrategy(new CustomSDCardLoader()) // 自定义加载策略,指定SDCard路径
.addStrategy(new ZipSDCardLoader()) // 自定义加载策略,获取zip包中的资源
.addInflater(new SkinAppCompatViewInflater()) // 基础控件换肤
SkinAppCompatViewInflater.java
private View createViewFromFV(Context context, String name, AttributeSet attrs) {
View view = null;
if (name.contains(".")) {
return null;
}
switch (name) {
case "View":
view = new SkinCompatView(context, attrs);
break;
case "LinearLayout":
view = new SkinCompatLinearLayout(context, attrs);
break;
case "RelativeLayout":
view = new SkinCompatRelativeLayout(context, attrs);
break;
case "FrameLayout":
view = new SkinCompatFrameLayout(context, attrs);
break;
case "TextView":
view = new SkinCompatTextView(context, attrs);
break;
case "ImageView":
view = new SkinCompatImageView(context, attrs);
break;
case "Button":
view = new SkinCompatButton(context, attrs);
break;
case "EditText":
view = new SkinCompatEditText(context, attrs);
break;
case "Spinner":
view = new SkinCompatSpinner(context, attrs);
break;
case "ImageButton":
view = new SkinCompatImageButton(context, attrs);
break;
case "CheckBox":
view = new SkinCompatCheckBox(context, attrs);
break;
case "RadioButton":
view = new SkinCompatRadioButton(context, attrs);
break;
case "RadioGroup":
view = new SkinCompatRadioGroup(context, attrs);
break;
case "CheckedTextView":
view = new SkinCompatCheckedTextView(context, attrs);
break;
case "AutoCompleteTextView":
view = new SkinCompatAutoCompleteTextView(context, attrs);
break;
case "MultiAutoCompleteTextView":
view = new SkinCompatMultiAutoCompleteTextView(context, attrs);
break;
case "RatingBar":
view = new SkinCompatRatingBar(context, attrs);
break;
case "SeekBar":
view = new SkinCompatSeekBar(context, attrs);
break;
case "ProgressBar":
view = new SkinCompatProgressBar(context, attrs);
break;
case "ScrollView":
view = new SkinCompatScrollView(context, attrs);
break;
default:
break;
}
return view;
}
8、SkinCompatTextView.java 以TextView.颜色不同皮肤设置为例
public SkinCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mBackgroundTintHelper = new SkinCompatBackgroundHelper(this);
mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper = SkinCompatTextHelper.create(this);
mTextHelper.loadFromAttributes(attrs, defStyleAttr);
}
SkinCompatTextHelper.java
@Override
public void applySkin() {
applyCompoundDrawablesRelativeResource();
applyTextColorResource();
applyTextColorHintResource();
}
mTextColorResId = checkResourceId(mTextColorResId);
if (mTextColorResId != INVALID_ID) {
// TODO: HTC_U-3u OS:8.0上调用framework的getColorStateList方法,有可能抛出异常,暂时没有找到更好的解决办法.
// issue: https://github.com/ximsfei/Android-skin-support/issues/110
try {
ColorStateList color = SkinCompatResources.getColorStateList(mView.getContext(), mTextColorResId);
mView.setTextColor(color);
} catch (Exception e) {
}
}
}
SkinCompatResources.java
private ColorStateList getSkinColorStateList(Context context, int resId) {
if (!SkinCompatUserThemeManager.get().isColorEmpty()) {
ColorStateList colorStateList = SkinCompatUserThemeManager.get().getColorStateList(resId);
if (colorStateList != null) {
return colorStateList;
}
}
if (mStrategy != null) {
ColorStateList colorStateList = mStrategy.getColorStateList(context, mSkinName, resId);
if (colorStateList != null) {
return colorStateList;
}
}
if (!isDefaultSkin) {
int targetResId = getTargetResId(context, resId);
if (targetResId != 0) {
return mResources.getColorStateList(targetResId);
}
}
return context.getResources().getColorStateList(resId);
}
int getTargetResId(Context context, int resId) {
try {
String resName = null;
if (mStrategy != null) {
resName = mStrategy.getTargetResourceEntryName(context, mSkinName, resId);
}
if (TextUtils.isEmpty(resName)) {
resName = context.getResources().getResourceEntryName(resId);
}
String type = context.getResources().getResourceTypeName(resId);
return mResources.getIdentifier(resName, type, mSkinPkgName);
} catch (Exception e) {
// 换肤失败不至于应用崩溃.
return 0;
}
}
9、SkinBuildInLoader.java 以资源id +"_"+皮肤名称获取
@Override
public String getTargetResourceEntryName(Context context, String skinName, int resId) {
return context.getResources().getResourceEntryName(resId) + "_" + skinName;
}