Android_ Realization of skin changing function

Posted by vickytam on Wed, 15 Dec 2021 21:06:37 +0100

Android skin changing function is nothing new. There are many third-party skin changing libraries and implementation schemes on the market. The main reasons for choosing Tencent's QMUI library to demonstrate the skin changing function of APP are as follows: 1. The implementation process of skin changing function is simple and easy to understand; 2. It can easily adapt to the Dark Mode provided by Android 10; 3. It can also whitewash various components and effects of QMUI (this is important, 😁 Ha ha ~);

1. Realization of skin changing process:

1.1 new project

Create an empty project through Android studio (the process of creating a project is omitted), and add QMUI dependency:

implementation 'com.qmuiteam:qmui:2.0.0-alpha10'

1.2. Define attr and its implementation style (emphasis)

This step requires us to work with designers to sort out a set of color and background resources for App use. Then we name it in the form of attr in xml. This project case:

src/main/res/values/styles.xml:

<resources>
        <attr name="colorPrimary" format="color" />
        <attr name="colorBg1" format="color" />
        <attr name="colorBg2" format="color" />
        <attr name="colorBg3" format="color" />
        <attr name="colorTextWhite" format="color" />

        <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
            <item name="colorPrimary">@color/colorPrimaryDefault</item>
            <item name="colorBg1">@color/colorBgDefault1</item>
            <item name="colorBg2">@color/colorBgDefault2</item>
            <item name="colorBg3">@color/colorBgDefault3</item>
            <item name="colorTextWhite">@color/colorTextWhite</item>
        </style>

        <style name="app_skin_1" parent="AppTheme">
            <item name="colorPrimary">@color/colorPrimarySkin1</item>
            <item name="colorBg1">@color/colorBgDefault1Skin1</item>
            <item name="colorBg2">@color/colorBgDefault1Skin2</item>
            <item name="colorBg3">@color/colorBgDefault1Skin3</item>
        </style>

        <style name="app_skin_2" parent="AppTheme">
            <item name="colorPrimary">@color/colorPrimarySkin2</item>
            <item name="colorBg1">@color/colorBgDefault2Skin1</item>
            <item name="colorBg2">@color/colorBgDefault2Skin2</item>
            <item name="colorBg3">@color/colorBgDefault2Skin3</item>
        </style>
    </resources>

src/main/res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>
    <resources>
        <color name="colorPrimaryDefault">#FCE4EC</color>
        <color name="colorBgDefault1">#F06292</color>
        <color name="colorBgDefault2">#EC407A</color>
        <color name="colorBgDefault3">#880E4F</color>
        <color name="colorTextWhite">#FFFFFF</color>

        <color name="colorPrimarySkin1">#E3F2FD</color>
        <color name="colorBgDefault1Skin1">#90CAF9</color>
        <color name="colorBgDefault1Skin2">#42A5F5</color>
        <color name="colorBgDefault1Skin3">#0D47A1</color>

        <color name="colorPrimarySkin2">#FAFAFA</color>
        <color name="colorBgDefault2Skin1">#757575</color>
        <color name="colorBgDefault2Skin2">#424242</color>
        <color name="colorBgDefault2Skin3">#212121</color>
    </resources>

style supports inheritance. Take the above example, app\_skin\_1 inherits from AppTheme. When looking for its value through attr, if it is in app\_skin\_1 if it doesn't find it, it will go to AppTheme to look for it. Therefore, we can take the theme of the App as our skin, and other skins inherit from this skin.

1.3 custom skin change management

Different skins and colors of APP have been defined. We need to define a class to interface with QMUI to manage these skins. The code functions include skin loading, switching and other operations.

src/main/java/com/qxc/testandroid/QDSkinManager.java:

package com.qxc.testandroid;

    import android.content.Context;
    import android.content.res.Configuration;

    import com.qmuiteam.qmui.skin.QMUISkinManager;

    public class QDSkinManager {
        public static final int SKIN_DEFAULT = 1;
        public static final int SKIN_1 = 2;
        public static final int SKIN_2 = 3;

        public static void install(Context context) {
            QMUISkinManager skinManager = QMUISkinManager.defaultInstance(context);
            skinManager.addSkin(SKIN_DEFAULT, R.style.AppTheme);
            skinManager.addSkin(SKIN_1, R.style.app_skin_1);
            skinManager.addSkin(SKIN_2, R.style.app_skin_2);

            boolean isDarkMode = (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
            int storeSkinIndex = QDPreferenceManager.getInstance(context).getSkinIndex();
            if (isDarkMode && storeSkinIndex != SKIN_2) {
                skinManager.changeSkin(SKIN_2);
            } else if (!isDarkMode && storeSkinIndex == SKIN_1) {
                skinManager.changeSkin(SKIN_1);
            }else{
                skinManager.changeSkin(storeSkinIndex);
            }
        }

        public static void changeSkin(int index) {
            QMUISkinManager.defaultInstance(QDApplication.getContext()).changeSkin(index);
            QDPreferenceManager.getInstance(QDApplication.getContext()).setSkinIndex(index);
        }

        public static int getCurrentSkin() {
            return QMUISkinManager.defaultInstance(QDApplication.getContext()).getCurrentSkin();
        }
    }

1.4. Custom skin saving class

After we switch the skin, we need to save the switched skin information. When we start the APP next time, we will load the switched skin directly.

src/main/java/com/qxc/testandroid/QDPreferenceManager.java:

package com.qxc.testandroid;

    import android.content.Context;
    import android.content.SharedPreferences;
    import android.preference.PreferenceManager;

    public class QDPreferenceManager {
        private static SharedPreferences sPreferences;
        private static QDPreferenceManager sQDPreferenceManager = null;

        private static final String APP_VERSION_CODE = "app_version_code";
        private static final String APP_SKIN_INDEX = "app_skin_index";

        private QDPreferenceManager(Context context) {
            sPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
        }

        public static final QDPreferenceManager getInstance(Context context) {
            if (sQDPreferenceManager == null) {
                sQDPreferenceManager = new QDPreferenceManager(context);
            }
            return sQDPreferenceManager;
        }

        public void setAppVersionCode(int code) {
            final SharedPreferences.Editor editor = sPreferences.edit();
            editor.putInt(APP_VERSION_CODE, code);
            editor.apply();
        }

        public void setSkinIndex(int index) {
            SharedPreferences.Editor editor = sPreferences.edit();
            editor.putInt(APP_SKIN_INDEX, index);
            editor.apply();
        }

        public int getSkinIndex() {
            return sPreferences.getInt(APP_SKIN_INDEX, QDSkinManager.SKIN_DEFAULT);
        }
    }

1.5 APP loads QDSkinManager and adapts to dark mode

This work only needs to be done once. It is recommended to customize the Application to realize this function.

src/main/java/com/qxc/testandroid/QDApplication.java:

package com.qxc.testandroid;

    import android.annotation.SuppressLint;
    import android.app.Application;
    import android.content.Context;
    import android.content.res.Configuration;

    import androidx.annotation.NonNull;

    public class QDApplication extends Application {

        @SuppressLint("StaticFieldLeak")
        private static Context context;

        public static Context getContext() {
            return context;
        }

        @Override
        public void onCreate() {
            super.onCreate();
            context = getApplicationContext();
            QDSkinManager.install(this);
        }

        @Override
        public void onConfigurationChanged(@NonNull Configuration newConfig) {
            super.onConfigurationChanged(newConfig);
            //Adapt to Dark Mode
            if ((newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES) {
                QDSkinManager.changeSkin(QDSkinManager.SKIN_2);
            } else if (QDSkinManager.getCurrentSkin() == QDSkinManager.SKIN_2) {
                QDSkinManager.changeSkin(QDSkinManager.SKIN_DEFAULT);
            }
        }
    }

Don't forget the Android manifest Specify our custom Application class in XML:

<application
            android:name=".QDApplication"
            ......

1.6. Start writing Activity

The basic work is ready. Next, we implement the defined skin change effect. Modify the layout file of MainActivity and write our UI layout:

src/main/res/layout/activity\_main.xml:

<?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:qmui_skin_background="?attr/colorPrimary"
        tools:context=".MainActivity">

        <RelativeLayout
            android:id="@+id/v1"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            app:qmui_skin_background="?attr/colorBg2" >
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                android:textSize="16sp"
                android:text="Title Bar"
                app:qmui_skin_text_color="?attr/colorTextWhite"/>
        </RelativeLayout>

        <RelativeLayout
            android:id="@+id/v2"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:layout_below="@id/v1"
            android:layout_marginTop="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            app:qmui_skin_background="?attr/colorBg1" />

        <com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton
            android:id="@+id/btn"
            android:layout_marginTop="10dp"
            android:layout_width="200dp"
            android:layout_height="50dp"
            android:layout_below="@id/v2"
            android:layout_centerHorizontal="true"
            android:gravity="center"
            app:qmui_radius="10dp"
            app:qmui_skin_background="?attr/colorBg3"
            app:qmui_skin_text_color="?attr/colorTextWhite"
            app:qmui_skin_border="?attr/colorBg2"
            android:text="change skin" />
    </RelativeLayout>

Note: to realize skin change, we should use the skin change attribute provided by QMUI when setting the control color:

app:qmui_skin_xxx

The QMUI official website has provided the following skin changing attributes for our use, which can meet the general development needs, as shown in the figure below:

Next, let's write the Activity code. In the Activity, we need to register QMUISkinManager before the Activity can enjoy the skin change function (Note: in actual development, if all APP pages need to support skin change, we try to write the registration of QMUISkinManager in BaseActivity).

There are two schemes to realize registration:

Option 1:

We can inherit QMUIFragmentActivity or QMUIActivity from the Activity class, so QMUISkinManager is injected by default

Scheme 2 (in order to let you know how to register, we choose this scheme. Don't worry, it's actually very simple):

We implement the registration and deregistration of QMUISkinManager ourselves

package com.qxc.testandroid;

    import android.app.Activity;
    import android.os.Bundle;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.widget.Button;

    import androidx.core.view.LayoutInflaterCompat;

    import com.qmuiteam.qmui.skin.QMUISkinLayoutInflaterFactory;
    import com.qmuiteam.qmui.skin.QMUISkinManager;

    public class MainActivity extends Activity {
        private QMUISkinManager skinManager;
        private Button btn;
        private int skinIndex;

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            // Using QMUISkinLayoutInflaterFactory
            LayoutInflater layoutInflater = LayoutInflater.from(this);
            LayoutInflaterCompat.setFactory2(layoutInflater, new QMUISkinLayoutInflaterFactory(this, layoutInflater));

            super.onCreate(savedInstanceState);

            // Inject QMUISkinManager
            skinManager = QMUISkinManager.defaultInstance(this);

            setContentView(R.layout.activity_main);

            initView();
            initEvent();
        }

        private void initView(){
            btn = findViewById(R.id.btn);
        }

        private void initEvent(){
            //Skin changing operation
            skinIndex = QDSkinManager.SKIN_DEFAULT;
            btn.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(skinIndex + 1 > 3){
                        skinIndex = 0;
                    }
                    skinIndex += 1;
                    QDSkinManager.changeSkin(skinIndex);
                }
            });
        }

        @Override
        protected void onPause() {
            super.onPause();
        }

        @Override
        public void onStart() {
            super.onStart();
            //Register QDSkinManager
            if(skinManager != null){
                skinManager.register(this);
            }
        }

        @Override
        protected void onStop() {
            super.onStop();
            //Unregister QDSkinManager
            if(skinManager != null){
                skinManager.unRegister(this);
            }
        }
        @Override
        protected void onResume() {
            super.onResume();
        }

        @Override
        protected void onDestroy() {
            super.onDestroy();
        }
    }

At this point, the coding is over.

2. Knowledge expansion

API provided by QMUI skinning:

  • QMUISkinManager: stores the skin color configuration and distributes the current skin color to the activities, fragments, Dialog and PopupWindow it manages. It passes through QMUISkinManager Of (name, context), which can be multi instance. Therefore, an App can perform different skin change management in different scenarios, such as skin change of reading product readers and differentiated management of uiMode switching of other business modules.
  • QMUISkinValueBuilder: used to build the skin changing configuration (textColor, background, border, separator, etc.) of a View instance
  • QMUISkinHelper: some auxiliary tools and methods. The most commonly used is QMUISkinHelper Setskinvalue (View, QMUISkinValueBuilder) applies the configuration of QMUISkinValueBuilder to a View instance. If you use the kotlin language, you can use View skin { ... } To configure the View instance.
  • QMUISkinLayoutInflaterFactory: used to support xml skinning configuration item parsing.
  • Iqmuiskinndispatchinterceptor: view can implement it to intercept the distribution of skin changes.
  • IQMUISkinHandlerView: View can completely customize the processing of different skins by implementing it.
  • IQMUISkinDefaultAttrProvider: View can implement it to provide the default skin changing configuration of View and provide skin changing support from the component level.

end of document

Your favorite collection is my greatest encouragement! Welcome to follow my brief book, share Android dry goods and exchange Android technology. If you have any opinions on the article or any technical problems, please leave a message in the comment area for discussion!

This article is transferred from https://juejin.cn/post/7038482977914880008 , in case of infringement, please contact to delete.

Topics: Android