# # # 1. The overview


Read the first two articles, so now we can not only get resources from another plug-in skin package but also to block the creation of system View, So now all we have to do is write a little code to achieve a seamless skin change.

All shared outline: 2017Android road to progress with you

Video on address: http://pan.baidu.com/s/1nvv2Nln

###2. Create a Hook interception View


In order to improve the scalability of the framework, we created a layer of BaseSkinActivity. In this layer, we can intercept and create views. The premise is to be compatible with tinT and other functions. The following code we are to see the source code, Google engineers wrote the basic will not have a problem, see their source code considered very thoughtful, or can use for reference:

Public class BaseSkinActivity extends AppCompatActivity implements LayoutInflaterFactory {// Create View private Inflater SkinCompatViewInflater mSkinCompatViewInflater; Override protected void onCreate(Bundle savedInstanceState) {Override protected void onCreate(Bundle savedInstanceState) {Override protected void onCreate(Bundle savedInstanceState)  layoutInflater = LayoutInflater.from(this); LayoutInflaterCompat.setFactory(layoutInflater, this); super.onCreate(savedInstanceState); } @Override public View onCreateView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) { final boolean isPre21 = Build.VERSION.SDK_INT < 21;if (mSkinCompatViewInflater == null) {
            mSkinCompatViewInflater = new SkinCompatViewInflater();
        }

        // We only want the View to inherit its context if we're running pre-v21 final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent); View view = mSkinCompatViewInflater.createView(parent, name, context, attrs, inheritContext, isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */ true, /* Read read app:theme as a fallback at all times for legacy reasons */ VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */ ); if (view ! = null) { List
      
        skinAttrs = SkinSupport.getSkinView(context, attrs); if (skinAttrs.size() ! = 0) { addSkinManager(view, skinAttrs); } } return view; Private void addSkinManager(View View, List
       
         skinAttrs) {SkinView SkinView = new SkinView(View, skinAttrs); List
        
          skinViews = SkinManager.getInstance().getSkinViews(this); if (skinViews == null) { skinViews = new ArrayList<>(); SkinManager.getInstance().registerSkinView(skinViews, this); } skinViews.add(skinView); // If (skinManager.getInstance ().needChangeskin ()){skinManager.getInstance ().changeskin (skinView); } // From the system source code paste, 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(); }}}Copy the code

###3. Perfect the Management class SkinManager


I have also seen many third-party skin changing frames on the Internet, which are all quite good, but I always feel that they can not achieve the effect I want. In fact, I wrote it myself because I really wanted to kick the computer at that time. I didn’t solve a Bug for several days by myself, and I never felt so painful. Write yourself is not so big limitations, do not need to pay attention to configuration of what, directly a line to solve the problem.

/** * Created by Darren on 2017/3/20. * Email: [email protected] * Description: Public class SkinManager {private static SkinManager mInstance; private SkinResources mSkinResources; private Context mContext; private Map<ISkinChangeListener, List<SkinView>> mSkinViews = new HashMap<>(); // Give it the View to manage privateSkinManagerPublic void init(context context) {this.mcontext = context.getApplicationContext(); String skinPath = SkinPreUtils.getInstance(mContext).getSkinPath();if (TextUtils.isEmpty(skinPath)) {
            return;
        }

        File skinFile = new File(skinPath);
        if(! skinFile.exists()) { clearSkinInfo();return;
        }

        initSkinResource(skinPath);
    }

    static {
        mInstance = new SkinManager();
    }

    public static SkinManager getInstance() {
        returnmInstance; } /** * @param path Path of the current skin */ public int loadSkin(String path) {String currentSkinPath = SkinPreUtils.getInstance(mContext).getSkinPath();if (currentSkinPath.equals(path)) {
            return SkinConfig.SKIN_LOADED;
        }

        File skinFile = new File(path);
        if(! skinFile.exists()){returnSkinConfig.SKIN_PATH_ERROR; } // Check whether the signature is correct, initSkinResource(path); changeSkin(path); saveSkinInfo(path); // The load succeededreturnSkinConfig.SKIN_LOAD_SUCCESS; } /** * Switch skin ** @param path Current skin path */ private void changeSkin(String path) {for (ISkinChangeListener skinChangeListener : mSkinViews.keySet()) {
            List<SkinView> skinViews = mSkinViews.get(skinChangeListener);
            for(SkinView skinView : skinViews) { skinView.skin(); } skinChangeListener.changeSkin(path); } /** * Initializes the skin's Resource ** @param path */ private void initSkinResource(String path) {mSkinResources = new SkinResources(mContext, path); } /** * Save the current skin information ** @param path Current skin path */ private void saveSkinInfo(String path) { SkinPreUtils.getInstance(mContext).saveSkinPath(path); } /** * restore the default skin */ public voidrestoreDefault() {
        String currentSkinPath = SkinPreUtils.getInstance(mContext).getSkinPath();
        if (TextUtils.isEmpty(currentSkinPath)) {
            return; } String path = mContext.getPackageResourcePath(); initSkinResource(path); changeSkin(path); clearSkinInfo(); } /** * Empty the skin information */ private voidclearSkinInfo() {
        SkinPreUtils.getInstance(mContext).clearSkinInfo();
    }

    public SkinResources getSkinResources() {
        returnmSkinResources; Public void register(List<SkinView> skinViews, ISkinChangeListener skinChangeListener) { mSkinViews.put(skinChangeListener, skinViews); } public List<SkinView> getSkinViews(Activity activity) {return mSkinViews.get(activity);
    }

    public boolean isChangeSkin() {
        returnSkinPreUtils.getInstance(mContext).needChangeSkin(); } public void changeSkin(SkinView skinView) { skinView.skin(); } /** * remove the callback, Public void unregister(ISkinChangeListener skinChangeListener) {mskinviews. remove(skinChangeListener); }}Copy the code

###4. Final peels for use and testing


The premise is that all of our activities must inherit from BaseSkinActivity, and then we can use SkinManager’s loadSkin() method anywhere to achieve seamless switching without restarting the app. At present, although the whole skin change framework only a dozen categories, but something is ok to optimize or increase the function of what. Weird custom View that you can only do a little bit in the Activity, this is still not a way to solve the problem.

public class MainActivity extends BaseSkinActivity{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); } public void skin(View view) { // 1. First go online to download resources/skin / 2. Download after stored in the local String skinPath = Environment. External.getexternalstoragedirectory () getAbsolutePath () + File.separator +"skin1.skin"; Int result = skinManager.getInstance ().loadSkin(skinPath); } public void skin1(View View) {// Restore the default skinManager.getInstance ().restoredefault (); } public void skin2(View View) {Intent Intent = new Intent(this, mainactivity.class); startActivity(intent); }}Copy the code

All shared outline: 2017Android road to progress with you

Video on address: http://pan.baidu.com/s/1nvv2Nln