编程知识 cdmana.com

Recherche sur le changement de peau Android

Contexte

AndroidLa technique de changement de peau est une technique qui a été mature il y a longtemps,L'entreprise a besoin de peau.Pour ne pas refaire les roues,Et répondre rapidement aux besoins,Et la stabilité,Deux cadres principaux ont été trouvés plus populaires,Android-Skin-LoaderEtAndroid-skin-support

Android-Skin-Loader

GitHub - fengjundev/Android-Skin-Loader: Un cadre de peau pour le changement de peau en chargeant dynamiquement les paquets de peau locaux Insérer la description de l'image ici Vous pouvez voir que personne n'a été entretenu depuis des années,Il y a un problème et il n'est pas facile de le résoudre(Il n'y a pas ici de moindre intention de dévaloriser le cadre)

Voici le principe,AdoptionLayoutInflater.setFactoryDe la façon dont, Dans le rappelonCreateViewAnalyser chaqueViewDeattrs, .Déterminer s'il y a des attributs marqués qui nécessitent un changement de peau, Par exemple,background, textColor, Ou si la ressource correspondante estskin_Début, etc.. Puis sauvegardezmapMoyenne, Pour chaqueViewFais - le.for Boucle à travers tout attr, Pour plus d'attributs , BesoinActivityInterface d'implémentation, Va avoir besoin d'un changement de peau View, Et les attributs correspondants

Android-skin-support

[Impossible de transférer l'image de la chaîne externe(img-N53580LB-1569138768035)(:storage/b880fd6d-37c9-45b8-894a-22181a6c4adf/aab99155.png)]
[Impossible de transférer l'image de la chaîne externe(img-N53580LB-1569138768035)(:storage/b880fd6d-37c9-45b8-894a-22181a6c4adf/aab99155.png)]

GithubPrécédentstar Plus de cadres de peau -Android-skin-support( C'est fait par cœur. Android Cadre de changement de peau, Très faible coût d'apprentissage , Excellente expérience utilisateur . Une seule ligne de code peut changer la peau , Tu mérites!!!). Après une simple compréhension, On peut le faire rapidement. , Et très peu intrusif ,Adresse du code source: https://github.com/ximsfei/Android-skin-support

Introduction

SkinCompatManager.withoutActivity(this).loadSkin(); C'est si simple., La tienne.APK A une fonction de peau puissante , Bien sûr, il a maintenant une fonction de peau. , N'oubliez pas de faire des sacs de peau .

Fonction

Soutenir le changement de peau des ressources utilisées dans la mise en page . Prise en charge de la peau de ressource définie dans le Code . La plupart des contrôles de base sont pris en charge par défaut ,Material DesignChangement de peau. Prise en charge de la définition dynamique des valeurs de couleur du thème ,Options de soutiensdcard L'image sur drawable Ressources de changement de peau . Prend en charge plusieurs politiques de chargement (Application interne/Type plug - in/PersonnalisationsdcardChemin/zip Autres ressources, etc. ). Priorité de chargement des ressources : Configuration dynamique des ressources - Charger les ressources dans la politique - Changement de peau plug - in / Application d’un changement de peau interne -Ressources d'application. Prise en charge de la personnalisation, Sélectionnez le chargement du module désiré . Support Vector graph (vector/svg)Changement de peau. skin-support 4.0.0Soutien ci - dessusAndroidX,4.0.0 Soutien suivant supportBibliothèque Pour plus de détails, veuillez consulter directement les instructions officielles,Très détaillé.

Alors comment est - ce possible? , Voici quelques connaissances préliminaires.

AppCompatActivityRéalisation

Crache.,Google Pour mettre à jour les développeurs androidx,support28 Version de nombreuses bibliothèques ne fournissent pas de code source ,Vous l'avez peut - être découvert., Revenons à la question.

public class AppCompatActivity extends FragmentActivity {
   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       final AppCompatDelegate delegate = getDelegate();
       delegate.installViewFactory();
       delegate.onCreate(savedInstanceState);
       ...
   }

   @Override
   public MenuInflater getMenuInflater() {
       return getDelegate().getMenuInflater();
   }

   @Override
   public void setContentView(@LayoutRes int layoutResID) {
       getDelegate().setContentView(layoutResID);
   }

   @Override
   public void setContentView(View view) {
       getDelegate().setContentView(view);
   }
   ....
}

AppCompatActivity A délégué la majeure partie de son cycle de vie AppCompatDelegate

Diagramme de classe Principalement utilisé dans le code source AppCompateDelegateSous - classeAppCompatDelegateImpl

class AppCompatDelegateImpl extends AppCompatDelegate implements Callback, Factory2

 public void installViewFactory() {
        LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
        if (layoutInflater.getFactory() == null) {
            LayoutInflaterCompat.setFactory2(layoutInflater, this);
        } else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
            Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
        }

    }

AppCompatDelegateImplMoyenne, InLayoutInflaterFactoryMéthode d'interface pouronCreateView Lieutenant - généralView La création de AppCompatViewInflater

public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
        if (this.mAppCompatViewInflater == null) {
            TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
            String viewInflaterClassName = a.getString(styleable.AppCompatTheme_viewInflaterClass);
            if (viewInflaterClassName != null && !AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
                try {
                    Class viewInflaterClass = Class.forName(viewInflaterClassName);
                    this.mAppCompatViewInflater = (AppCompatViewInflater)viewInflaterClass.getDeclaredConstructor().newInstance();
                } catch (Throwable var8) {
                    Log.i("AppCompatDelegate", "Failed to instantiate custom view inflater " + viewInflaterClassName + ". Falling back to default.", var8);
                    this.mAppCompatViewInflater = new AppCompatViewInflater();
                }
            } else {
                this.mAppCompatViewInflater = new AppCompatViewInflater();
            }
        }

        boolean inheritContext = false;
        if (IS_PRE_LOLLIPOP) {
            inheritContext = attrs instanceof XmlPullParser ? ((XmlPullParser)attrs).getDepth() > 1 : this.shouldInheritContext((ViewParent)parent);
        }
       // On peut regarder directement ici. 
        return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());
    }

Encore une foisAppCompatViewInflaterMoyennecreateViewRéalisation

public final View createView(View parent, final String name, @NonNull Context context,
        @NonNull AttributeSet attrs, boolean inheritContext,
        boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
    ......
    View view = null;
    switch (name) {
        case "TextView":
            view = new AppCompatTextView(context, attrs);
            break;
        case "ImageView":
            view = new AppCompatImageView(context, attrs);
            break;
        case "Button":
            view = new AppCompatButton(context, attrs);
            break;
        case "EditText":
            view = new AppCompatEditText(context, attrs);
            break;
        case "Spinner":
            view = new AppCompatSpinner(context, attrs);
            break;
        case "ImageButton":
            view = new AppCompatImageButton(context, attrs);
            break;
        case "CheckBox":
            view = new AppCompatCheckBox(context, attrs);
            break;
        ......
    }
    ......
    return view;
}

Regardons une de ces classes. AppCompatTextViewRéalisation

public class AppCompatTextView extends TextView implements TintableBackgroundView {
	//Voilà.2 L'une est la classe clé 
    private final AppCompatBackgroundHelper mBackgroundTintHelper;
    private final AppCompatTextHelper mTextHelper;
    
    public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(TintContextWrapper.wrap(context), attrs, defStyleAttr);

        mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
        mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);

        mTextHelper = AppCompatTextHelper.create(this);
        mTextHelper.loadFromAttributes(attrs, defStyleAttr);
        mTextHelper.applyCompoundDrawablesTints();
    }

    @Override
    public void setBackgroundResource(@DrawableRes int resId) {
        super.setBackgroundResource(resId);
        if (mBackgroundTintHelper != null) {
            mBackgroundTintHelper.onSetBackgroundResource(resId);
        }
    }
    ......
}

Le code source peut être intercepté ViewProcessus de création, Remplacer certains composants de base , Et puis il y a des propriétés spéciales (eg: background, textColor) On s'en occupe., C'est l'idée centrale du cadre de changement de peau ,Une série d'opérations de ce cadre se déroule autour de

InterceptionDelegate

Si utilisé dans le projet ActivityDeAppCompatActivity,Surcharge requisegetDelegate()Méthodes Il y a un trou ici,Non utiliséAppCompatActivityEn utilisant l'un des cadres précédentsBaseActivity,Je ne savais pas qu'il y avait cette classe de base au début, Je pensais que tout ActivityTous les deux., Après avoir lu le code source, j'ai compris.

@NonNull
@Override
public AppCompatDelegate getDelegate() {
    return SkinAppCompatDelegateImpl.get(this, this);
}

Processus de création d'un contrôle

Commençons par l'initialisation de la bibliothèque ,Ici.ApplicationEntrée, J'en ai ajouté un autre. SkinAppCompatViewInflater

SkinCompatManager.withoutActivity(application)
                .addInflater(new SkinAppCompatViewInflater());

SkinAppCompatViewInflater Est en fait utilisé pour créer ViewDe.Voyons voir.withoutActivity(application)Qu'est - ce qui s'est passé?.

//SkinCompatManager.java
/**
     *  Initialisation du cadre de changement de peau ,ÉcouterActivityCycle de vie.  Initialisation par cette méthode ,En applicationActivityPas besoin d'héritage{@link skin.support.app.SkinCompatActivity}.
     */
public static SkinCompatManager withoutActivity(Application application) {
    init(application);
    SkinActivityLifecycle.init(application);
    return sInstance;
}

//SkinActivityLifecycle.java
public static SkinActivityLifecycle init(Application application) {
    if (sInstance == null) {
        synchronized (SkinActivityLifecycle.class) {
            if (sInstance == null) {
                sInstance = new SkinActivityLifecycle(application);
            }
        }
    }
    return sInstance;
}
private SkinActivityLifecycle(Application application) {
    //C'est ici.,EnregistréActivityLifecycleCallbacks, Peut écouter tout ActivityLe cycle de vie de
    application.registerActivityLifecycleCallbacks(this);
    //C'est important, On verra ça plus tard. 
    installLayoutFactory(application);
    SkinCompatManager.getInstance().addObserver(getObserver(application));
}

Je vois., Lors de l'initialisation SkinActivityLifecycle En fait, c'est enregistré. ActivityLifecycleCallbacks, Pour pouvoir écouter appTous lesActivityLe cycle de vie de. Viens voir.SkinActivityLifecycle J'ai entendu. ActivityDeonCreate() Qu'est - ce qui s'est passé?

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    // Déterminer si un changement de peau est nécessaire    Ceci peut être contrôlé lors de l'initialisation externe 
    if (isContextSkinEnable(activity)) {
        //InActivityAu moment de la création,Va directementFactory Dans une bibliothèque tripartite 
        installLayoutFactory(activity);

        // Mettre à jour la couleur de la barre d'état 
        updateStatusBarColor(activity);
        //Mise à jourwindowCouleur de fond
        updateWindowBackground(activity);
        if (activity instanceof SkinCompatSupportable) {
            ((SkinCompatSupportable) activity).applySkin();
        }
    }
}

/**
    * ParamètresFactory(CréationViewL'usine de)
    */
 private void installLayoutFactory(Context context) {
        try {
            LayoutInflater layoutInflater = LayoutInflater.from(context);
            //Regarde ça.getSkinDelegate
            LayoutInflaterCompat.setFactory(layoutInflater, getSkinDelegate(context));
        } catch (Exception e) {
            Slog.i("SkinActivity", "A factory has already been set on this LayoutInflater");
        }
    }

Dans notreActivityAu moment de la création, Commencez par déterminer si vous avez besoin d'un changement de peau , J'ai besoin d'un changement de peau. .

Voyons voirsetFactory() Processus de création du deuxième paramètre pour , Le deuxième paramètre est en fait une création ViewL'usine de.

//SkinActivityLifecycle.java
private SkinCompatDelegate getSkinDelegate(Context context) {
    if (mSkinDelegateMap == null) {
        mSkinDelegateMap = new WeakHashMap<>();
    }
	//ObtenirSkinCompatDelegate
    SkinCompatDelegate mSkinDelegate = mSkinDelegateMap.get(context);
    if (mSkinDelegate == null) {
        mSkinDelegate = SkinCompatDelegate.create(context);
        mSkinDelegateMap.put(context, mSkinDelegate);
    }
    return mSkinDelegate;
}

//SkinCompatDelegate.java
public class SkinCompatDelegate implements LayoutInflaterFactory {
    private final Context mContext;
    //Protagoniste  Ici Ici!!!
    private SkinCompatViewInflater mSkinCompatViewInflater;
    private List<WeakReference<SkinCompatSupportable>> mSkinHelpers = new ArrayList<>();

    private SkinCompatDelegate(Context context) {
        mContext = context;
    }

    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        View view = createView(parent, name, context, attrs);

        if (view == null) {
            return null;
        }
        if (view instanceof SkinCompatSupportable) {
            mSkinHelpers.add(new WeakReference<>((SkinCompatSupportable) view));
        }

        return view;
    }

    public View createView(View parent, final String name, @NonNull Context context,
                           @NonNull AttributeSet attrs) {
        if (mSkinCompatViewInflater == null) {
            mSkinCompatViewInflater = new SkinCompatViewInflater();
        }

        List<SkinWrapper> wrapperList = SkinCompatManager.getInstance().getWrappers();
        for (SkinWrapper wrapper : wrapperList) {
            Context wrappedContext = wrapper.wrapContext(mContext, parent, attrs);
            if (wrappedContext != null) {
                context = wrappedContext;
            }
        }
        //Regarde ça.
        return mSkinCompatViewInflater.createView(parent, name, context, attrs);
    }

    public static SkinCompatDelegate create(Context context) {
        return new SkinCompatDelegate(context);
    }

    public void applySkin() {
        if (mSkinHelpers != null && !mSkinHelpers.isEmpty()) {
            for (WeakReference ref : mSkinHelpers) {
                if (ref != null && ref.get() != null) {
                    ((SkinCompatSupportable) ref.get()).applySkin();
                }
            }
        }
    }
}

Je vois.SkinCompatDelegateC'est unSkinCompatViewInflaterMandat. Lorsque le système doit créer ViewQuand,Je vais [email protected] public View onCreateView(View parent, String name, Context context, AttributeSet attrs)Méthodes, Parce que c'est réglé à l'avant LayoutInflaterDeFactoryPourSkinCompatDelegate. Et puisSkinCompatDelegateSera crééViewLe travail deSkinCompatViewInflaterVa t'en occuper.

Viens voir.SkinCompatViewInflaterComment créerViewDe

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;
    //Ici.SkinLayoutInflater C'est ce que nous avons défini lors de l'initialisation SkinAppCompatViewInflater
    //Bien sûr.,SkinLayoutInflaterIl peut y en avoir plusieurs.
    for (SkinLayoutInflater inflater : SkinCompatManager.getInstance().getInflaters()) {
        view = inflater.createView(context, name, attrs);
        if (view == null) {
            continue;
        } else {
            break;
        }
    }
    return view;
}

public View createViewFromTag(Context context, String name, AttributeSet attrs) {
    if ("view".equals(name)) {
        name = attrs.getAttributeValue(null, "class");
    }
    try {
        mConstructorArgs[0] = context;
        mConstructorArgs[1] = attrs;

        //Contrôles personnalisés
        if (-1 == name.indexOf('.')) {
            for (int i = 0; i < sClassPrefixList.length; i++) {
             final View view = createView(context, name, sClassPrefixList[i]);
                if (view != null) {
                    return view;
                }
            }
            return null;
        } else {
            return createView(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;
    }

createViewFromInflater()La méthode utilise ce que nous avons défini lors de l'initialisation de la BibliothèqueSkinLayoutInflaterPour créerview. Pourquoi?SkinCompatViewInflater Il faut affiner. , Il faut que ce soit plus fin. SkinLayoutInflaterPour s'en occuper? C'est parce qu'il est facile de l'étendre. , Plusieurs exemples sont donnés dans la bibliothèque SkinLayoutInflater,Oui.SkinAppCompatViewInflater( Constructeur de contrôle de base )、SkinMaterialViewInflater(material design Constructeur de contrôle )、SkinConstraintViewInflater(ConstraintLayoutConstructeur)、SkinCardViewInflater(CardView v7Constructeur). Parce qu'au moment de l'initialisation, nous définissons SkinAppCompatViewInflater, Les autres constructeurs ont des principes similaires .Voyons voirSkinAppCompatViewInflater

//SkinAppCompatViewInflater.java
@Override
public View createView(Context context, String name, AttributeSet attrs) {
    View view = createViewFromFV(context, name, attrs);

    if (view == null) {
        view = createViewFromV7(context, name, attrs);
    }
    return view;
}

private View createViewFromFV(Context context, String name, AttributeSet attrs) {
    View view = null;
    if (name.contains(".")) {
        return null;
    }
    switch (name) {
       //Voilà le point., Remplacer par un contrôle qui change la peau 
        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;
        ......
        default:
            break;
    }
    return view;
}

private View createViewFromV7(Context context, String name, AttributeSet attrs) {
    View view = null;
    switch (name) {
        case "android.support.v7.widget.Toolbar":
        	// Remplacer par un contrôle qui change la peau 
            view = new SkinCompatToolbar(context, attrs);
            break;
        default:
            break;
    }
    return view;
}

Nous sommes ici pour View Interception de la création de , Puis créez vos propres contrôles . Puisque c'est un contrôle que nous avons créé nous - mêmes, , C'est facile de faire ce que tu veux.

Voyons voir.SkinCompatTextViewSource de

//SkinCompatTextView.java
public class SkinCompatTextView extends AppCompatTextView implements SkinCompatSupportable {
    private SkinCompatTextHelper mTextHelper;
    private SkinCompatBackgroundHelper mBackgroundTintHelper;

    public SkinCompatTextView(Context context) {
        this(context, null);
    }

    public SkinCompatTextView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.textViewStyle);
    }

    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);
    }
    ......

    @Override
    public void applySkin() {
        if (mBackgroundTintHelper != null) {
            mBackgroundTintHelper.applySkin();
        }
        if (mTextHelper != null) {
            mTextHelper.applySkin();
        }
    }

}

C'est fait.SkinCompatSupportableInterface, Il sera rappelé lors de l'opération de changement de peau applySkinMéthodes,Dans cette approche background Les attributs associés sont remis à SkinCompatBackgroundHelperVa t'en occuper.,textColor Les opérations pertinentes sont confiées à: SkinCompatTextHelperVa t'en occuper..

Chargement de la peau à partir du paquet cutané

Ce qui précède est un processus d'initialisation , Cliquez pour changer la peau. , Comment fonctionne - t - il? En fait, le sac à peau est un apk, Mais il n'y a pas de code dedans. ,Il n'y a que des ressources ou des couleurs qui ont besoin d'un changement de peau. Et les noms de ces ressources doivent être les mêmes que ceux actuels app Les noms des ressources sont cohérents , Pour remplacer .

Comment utiliser

SkinCompatManager.getInstance().loadSkin("night.skin", null, CustomSDCardLoader.SKIN_LOADER_STRATEGY_SDCARD);

Directement à partir deloadSkin()Comment?

/**
* Charger le sac de peau.
* @param skinName  Nom du sac cutané .
* @param listener Surveillance du chargement des paquets de peau.
* @param strategy Politique de chargement des paquets de peau.
*/
public AsyncTask loadSkin(String skinName, SkinLoaderListener listener, int strategy) {
    //Politique de chargement   Divisé en plusieurs :DeSD Charger la peau dans la carte ,Deassets Chargement de la peau dans le fichier, etc. 
    SkinLoaderStrategy loaderStrategy = mStrategyMap.get(strategy);
    if (loaderStrategy == null) {
        return null;
    }
    return new SkinLoadTask(listener, loaderStrategy).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, skinName);
}

Je vois.SkinLoadTaskC'est unAsyncTask, Ensuite, analysez ce sac en arrière - plan. .Puisque c'est le cas,AsyncTask,Alors regardonsdoInBackground()Méthodes

Voyons voir.SkinLoadTaskDedoInBackground()

//SkinLoadTask.java
@Override
protected String doInBackground(String... params) {
    ......
    try {
        if (params.length == 1) {
            // Charger la peau en arrière - plan selon la politique de chargement 
            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;
}

//Politique de chargement  Choisissez celui que vous voulez.  SkinSDCardLoader.java  DeSD Peau chargée de cartes 
@Override
public String loadSkinInBackground(Context context, String skinName) {
    if (TextUtils.isEmpty(skinName)) {
        return skinName;
    }
    // Obtenir le chemin de la peau 
    String skinPkgPath = getSkinPath(context, skinName);
    if (SkinFileUtils.isFileExists(skinPkgPath)) {
        // Obtenir le nom du sac de peau .
        String pkgName = SkinCompatManager.getInstance().getSkinPackageName(skinPkgPath);
        // Obtenir un sac de peau Resources
        Resources resources = SkinCompatManager.getInstance().getSkinResources(skinPkgPath);
        if (resources != null && !TextUtils.isEmpty(pkgName)) {
        //Resources,Nom du paquet, Nom de la peau , Enregistrer toutes les politiques de chargement 
            SkinCompatResources.getInstance().setupSkin(
                    resources,
                    pkgName,
                    skinName,
                    this);
            return skinName;
        }
    }
    return null;
}

//SkinCompatManager.java
// Obtenir le nom du sac de peau .
public String getSkinPackageName(String skinPkgPath) {
    PackageManager mPm = mAppContext.getPackageManager();
    PackageInfo info = mPm.getPackageArchiveInfo(skinPkgPath, PackageManager.GET_ACTIVITIES);
    return info.packageName;
}
// Obtenir les ressources du paquet peau {@link Resources}.
@Nullable
public Resources getSkinResources(String skinPkgPath) {
    try {
        AssetManager assetManager = AssetManager.class.newInstance();
        Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
        addAssetPath.invoke(assetManager, skinPkgPath);

        Resources superRes = mAppContext.getResources();
        return new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

C'est probablement aller au sous - thread pour obtenir le nom du paquet de peau etResources(Plus tard, nous avons besoin d'obtenir la couleur ou la ressource dans le paquet de peau à travers ceci). SkinCompatResources.getInstance().setupSkin()C'est en les chargeant à partir d'un paquet de peauResources,Nom du paquet, Nom de la peau , Enregistrer toutes les politiques de chargement .Avec tout ça,,Plus tard, on récupérera les ressources de la peau.

NotificationActivityChangement de peau

Indobackground Après la fin de la méthode

 @Override
        protected void onPostExecute(String skinName) {
           ······
          if (skinName != null) {
              SkinPreference.getInstance()
                .setSkinName(skinName)
                .setSkinStrategy(mStrategy.getType())
                .commitEditor();
                // Avis de changement de peau ,Mode observateur
              notifyUpdateSkin();
              ······
          } else {
              ······
          }

SkinObservableDenotifyUpdateSkin()Méthodes, Enregistré par cycle observers, Notification un par un Activity Commencer à changer de peau

C'est ici.observers D'où ça vient? ? On a trouvé où appeler. addObserver()C'est tout ce qu'il faut.Nous pouvons voir,InSkinActivityLifecycleDeonActivityResumed()Dans la méthode,Pour chaqueActivity Ont été ajoutés séparément SkinObservable

@Override
public void onActivityResumed(Activity activity) {
    mCurActivityRef = new WeakReference<>(activity);
    if (isContextSkinEnable(activity)) {
        LazySkinObserver observer = getObserver(activity);
        //Ici.
        SkinCompatManager.getInstance().addObserver(observer);
        observer.updateSkinIfNeeded();     
    }
 }

NotificationViewRafraîchirUI

RafraîchirUI Ça finira par arriver. LazySkinObserverDeupdateSkinForce()Méthodes

 void updateSkinForce() {
    ······
    if (mContext instanceof Activity && isContextSkinEnable(mContext)) {
        updateWindowBackground((Activity) mContext);
    }
    //NotificationViewPour rafraîchirUI
    getSkinDelegate(mContext).applySkin();
    // Notification réalisée SkinCompatSupportableDeActivityMise à jourUI
    if (mContext instanceof SkinCompatSupportable) {
         ((SkinCompatSupportable) mContext).applySkin();
    }
    ······
 }

Les contrôles définis dans la bibliothèque sont implémentés SkinCompatSupportableInterface, Facile à contrôler .Par exemple,SkinCompatTextViewDeapplySkin()Appelé dans la méthodeBackgroundTintHelperEtTextHelperDeapplySkin()Méthodes,C'est - à - dire que lorsque vous changez de peau, vous changez dynamiquement l'arrière - plan ou la couleur du texte ou quelque chose comme.Voyons voir.mBackgroundTintHelper.applySkin()Réalisation

//SkinCompatBackgroundHelper.java
@Override
public void applySkin() {
    // Le contrôle a - t - il un arrière - plan?   Détection
    mBackgroundResId = checkResourceId(mBackgroundResId);
    if (mBackgroundResId == INVALID_ID) {
        return;
    }
    Drawable drawable = SkinCompatVectorResources.getDrawableCompat(mView.getContext(), mBackgroundResId);
    if (drawable != null) {
        int paddingLeft = mView.getPaddingLeft();
        int paddingTop = mView.getPaddingTop();
        int paddingRight = mView.getPaddingRight();
        int paddingBottom = mView.getPaddingBottom();
        ViewCompat.setBackground(mView, drawable);
        mView.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
    }
}

Est d'obtenirdrawableEt voilà.viewDéfinir le contexte. La clé est d'obtenir drawableComment cela a - t - il été réalisé?. Voir la mise en œuvre concrète

//SkinCompatVectorResources.java
private Drawable getSkinDrawableCompat(Context context, int resId) {
    // Est actuellement une peau non par défaut 
    if (!SkinCompatResources.getInstance().isDefaultSkin()) {
        try {
            return SkinCompatDrawableManager.get().getDrawable(context, resId);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    ......
    return AppCompatResources.getDrawable(context, resId);
}

//SkinCompatDrawableManager.java
public Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {
    return getDrawable(context, resId, false);
}

Drawable getDrawable(@NonNull Context context, @DrawableRes int resId,
                        boolean failIfNotKnown) {
    //VérifiezDrawable Peut être décodé correctement 
    checkVectorDrawableSetup(context);

    Drawable drawable = loadDrawableFromDelegates(context, resId);
    if (drawable == null) {
        drawable = createDrawableIfNeeded(context, resId);
    }
    if (drawable == null) {
        //Voici la clé
        drawable = SkinCompatResources.getDrawable(context, resId);
    }

    if (drawable != null) {
        // Tint it if needed
        drawable = tintDrawable(context, resId, failIfNotKnown, drawable);
    }
    if (drawable != null) {
        // See if we need to 'fix' the drawable
        SkinCompatDrawableUtils.fixDrawable(drawable);
    }
    return drawable;
}

Enfin appelé SkinCompatDrawableManagerPour obtenirdrawable, Ici, nous allons créer drawable Utilisé à l'heure actuelle SkinCompatResourcesPour obtenir. Tu te souviens?SkinCompatResources- Oui.?C'est là qu'on a eu l'information sur le sac de peau, Enregistrer toutes les informations dans cette classe .

//SkinCompatResources.java
//DermatologiqueResourcesIl peut être utilisé pour obtenir des ressources dans la peau
private Resources mResources;
// Nom du sac cutané 
private String mSkinPkgName = "";
// Nom de la peau 
private String mSkinName = "";
//Politique de chargement
private SkinCompatManager.SkinLoaderStrategy mStrategy;
// Est la peau par défaut ?
private boolean isDefaultSkin = true;

public static Drawable getDrawable(Context context, int resId) {
    return getInstance().getSkinDrawable(context, resId);
}
/**
* Adoptionid Obtenir drawableRessources
* @param context Context
* @param resId   Ressourcesid
*/
private Drawable getSkinDrawable(Context context, int resId) {
    // Existe - t - il un cache de couleur de peau 
    if (!SkinCompatUserThemeManager.get().isColorEmpty()) {
        ColorStateList colorStateList = SkinCompatUserThemeManager.get().getColorStateList(resId);
        if (colorStateList != null) {
            return new ColorDrawable(colorStateList.getDefaultColor());
        }
    }
    // Avec ou sans peau drawableCache
    if (!SkinCompatUserThemeManager.get().isDrawableEmpty()) {
        Drawable drawable = SkinCompatUserThemeManager.get().getDrawable(resId);
        if (drawable != null) {
            return drawable;
        }
    }
    // La politique de chargement n'est pas vide    Peut être chargé par la politique de chargement drawable, Les développeurs peuvent personnaliser 
    if (mStrategy != null) {
        Drawable drawable = mStrategy.getDrawable(context, mSkinName, resId);
        if (drawable != null) {
            return drawable;
        }
    }
    // Peau non par défaut   Décharger les ressources de la peau 
    if (!isDefaultSkin) {
        // Ressources cutanées id   C'est notre but.
        int targetResId = getTargetResId(context, resId);
        if (targetResId != 0) {
            //Selonid Par la peau ResourcesPour obtenirdrawable
            return mResources.getDrawable(targetResId);
        }
    }
    return context.getResources().getDrawable(resId);
}

La politique est d'avoir des ressources de cache ( J'ai déjà pris ça dans un sac de peau. resIdRessources) Puis récupérer les ressources du cache , Pas de cache resId Par la peau ResourcesPour obtenirdrawable. Ici, A été obtenu dans le sac de peau drawable,C'est - à - dire qu'il permet de charger dynamiquement des images dans un paquet de peau,shape Et ainsi de suite. ,Le processus de chargement des couleurs dans la peau est similaire,Il n'y a pas beaucoup d'introduction ici à l'ensemble du processus de changement de peau est probablement comme ça.

Résumé des principes

  1. ÉcouterAPPTous lesActivityLe cycle de vie de(registerActivityLifecycleCallbacks())
  2. Dans chaqueActivityDeonCreate()Lorsque la méthode est appeléesetFactory(), Création de paramètres ViewL'usine de.Sera crééView Les détails de SkinCompatViewInflaterVa t'en occuper..
  3. La Bibliothèque elle - même a dépassé les contrôles du système (Par exemple,View Correspond à SkinCompatView), Réaliser l'interface peau - peau (Il n'y en a qu'un dans l'interfaceapplySkin()Méthodes), Indique que le contrôle supporte le changement de peau .Et rassembler ces contrôles après leur création, Facile à changer .
  4. Pour résoudre des attributs spéciaux à l'intérieur d'un contrôle que vous avez écrit dans la Bibliothèque(Par exemple,:background, textColor), Et l'enregistrer
  5. Lors du changement de peau , Traverser le cache une fois View, Appeler la méthode d'interface de sa mise en œuvre applySkin(),InapplySkin() Ressources de la peau (Vous pouvez obtenir des paquets de peau sur le Web ou localement)Accès aux ressources. Pour définir le contrôle d'une ressource après l'avoir obtenue backgroundOutextColorAttendez., Pour changer la peau .

Avantages( Citation de l'auteur original )

Quels sont les avantages d'une telle approche par rapport à d'autres cadres en ligne, Pourquoi faire des roues à plusieurs reprises

  1. Réduire les coûts d'utilisation du cadre en augmentant les coûts d'élaboration du cadre, Développement primaire, Tous lesAndroid Les développeurs en ont besoin. ;
  2. Le cadre de changement de peau est moins intrusif dans le Code d'entreprise, La méthode de réécriture de l'interface n'est pas nécessaire , Aucun autre code supplémentaire n'est nécessaire , Accès facile
  3. Source profonde, Similaire à la mise en œuvre du code source , Meilleure compatibilité.

Merci beaucoup:https://blog.csdn.net/ximsfei/article/details/54604310 https://github.com/ximsfei/Android-skin-support https://juejin.im/post/5b5b127d5188251acd0f3885 https://blog.csdn.net/yumi0629/article/details/80392906

版权声明
本文为[Le vent qui souffle sur les pages]所创,转载请带上原文链接,感谢
https://cdmana.com/2021/10/20211013231200942g.html

Scroll to Top