Android-skin-support 流程整理

禄豪
2023-12-01

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<String, Void, String> {
    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;
}
 类似资料: