天天看點

Android 主題Theme樣式一鍵換膚,非常簡單(附小案例)導語

Android 主題Theme樣式一鍵換膚,非常簡單(附小案例)導語

導語

    谷歌v7後的主題Theme其實就有意給開發者們開辟換膚的功能,我們一起手動制作一款可以換膚主題,開始撸碼吧!

一、統一自定義屬性名

attr.xml

<?xml version="1.0" encoding="utf-8"?>
    <resources>
        <attr name="mainColor" format="color" />
        <attr name="mainPrimaryTextColor" format="color" />
        <attr name="mainPrimaryLightTextColor" format="color" />
        <attr name="mainBgColor" format="color" />
    </resources>
           
屬性資源 attr 裡可以定義屬性類型:如color、float、integer、boolean、dimension(sp、dp/dip、px、pt…)、reference(指向本地資源)等等。

二、在Value檔案中自定義若幹套主題

style.xml

<resources>
 <!-- Base application theme. -->
    <style name="AppThemeNoAction" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="mainBgColor">#BBDEFB</item>
        <item name="mainColor">#2196F3</item>
    </style>

    <!--換膚-->
    <style name="AppThemeNoAction.Green">
        <item name="colorPrimary">#4CAF50</item>
        <item name="colorPrimaryDark">#388E3C</item>
        <item name="colorAccent">#F3F5F3</item>
        <item name="mainBgColor">#C8E6C9</item>
        <item name="mainColor">#4CAF50</item>
    </style>

    <style name="AppThemeNoAction.Blue">
        <item name="colorPrimary">#2196F3</item>
        <item name="colorPrimaryDark">#1976D2</item>
        <item name="colorAccent">#607D8B</item>
        <item name="mainBgColor">#BBDEFB</item>
        <item name="mainColor">#2196F3</item>
    </style>

    <style name="AppThemeNoAction.Yellow">
        <item name="colorPrimary">#FFC107</item>
        <item name="colorPrimaryDark">#FF9800</item>
        <item name="colorAccent">#927215</item>
        <item name="mainBgColor">#D1C4E9</item>
        <item name="mainColor">#FFC107</item>
    </style>

    <style name="AppThemeNoAction.Grey">
        <item name="colorPrimary">#607D8B</item>
        <item name="colorPrimaryDark">#455A64</item>
        <item name="colorAccent">#58575A</item>
        <item name="mainBgColor">#CFD8DC</item>
        <item name="mainColor">#607D8B</item>
    </style>
</resources>
           

三、指定自定義顔色

需要跟随主題切換顔色的控件,可以直接設定背景、字型顔色,字元串或圖檔屬性指派。

(1)xml中指派: 

<?xml version="1.0" encoding="utf-8"?>
    <Linerlayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/mainColor"
        android:minHeight="?attr/actionBarSize" />
           

(2)代碼中指派:

TypedArray a = obtainStyledAttributes(new int[]{R.attr.mainBgColor, 
    R.attr.mainColor});
  
  int color = a.getColor(0, Color.BLACK)

//或

  TypedValue typedValue = new TypedValue();

  newTheme.resolveAttribute(mAttrResId, typedValue, true)
           
這裡隻列出了顔色;還可以使用引用主題字元串以及圖檔。

四、工具類

public class ThemeUtil {

    public static class ThemeColors {
        public static final int THEME_GREEN = 1;
        public static final int ThEME_BLUE = 2;
        public static final int THEME_GREY = 3;
        public static final int THEME_YELLOW = 4;
    }

    public static void setBaseTheme(Context context) {
        SharedPreferences sharedPreferences = context.getSharedPreferences(
                "MyThemeShared", context.MODE_PRIVATE);
        int themeType = sharedPreferences.getInt("theme_type", 0);
        int themeId;
        switch (themeType) {
            case ThemeColors.THEME_GREEN:
                themeId = R.style.AppThemeNoAction_Green;
                break;
            case ThemeColors.ThEME_BLUE:
                themeId = R.style.AppThemeNoAction_Blue;
                break;

            case ThemeColors.THEME_GREY:
                themeId = R.style.AppThemeNoAction_Grey;
                break;
            case ThemeColors.THEME_YELLOW:
                themeId = R.style.AppThemeNoAction_Yellow;
                break;
            default:
                themeId = R.style.AppThemeNoAction;
        }
        context.setTheme(themeId);
    }

    public static boolean setNewTheme(Context context, int theme) {
        SharedPreferences sharedPreferences = context.getSharedPreferences(
                "MyThemeShared", context.MODE_PRIVATE);
        SharedPreferences.Editor e = sharedPreferences.edit();
        e.putInt("theme_type",theme);
//        e.apply();
        return  e.commit();//有傳回值
    }
}
           
要在Activity的onCreate方法裡的setContextView前使用了。這裡最好寫在BaseActivity中,更具share儲存的樣式值,來動态設定theme。

五、開始切換主題

@Override
protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        //設定主題
        ThemeUtil.setBaseTheme(this);

        setContentView(R.layout.activity_main);

}

private void setChangeTheme(){

       if (ThemeUtil.setNewTheme(mContext, ThemeUtil.ThemeColors.ThEME_BLUE)){
              //重新開機Activity
              recreate();
       }
}


           
當然直接設定後立即生效體驗會好點。但由于系統限制,正常情況都需要重新開機Activity,是以會出現閃屏問題。下面有個解決方案,就是動态擷取控件的atts值,對每一個控件進行操作,但是有點繁瑣。
ListView  mNewsListView = (ListView) findViewById(R.id.listview);

    // 為ListView設定要修改的屬性,在這裡沒有對ListView本身的屬性做修改
    ViewGroupSetter listViewSetter = new ViewGroupSetter(mNewsListView, 0);
    // 綁定ListView的Item View中的news_title視圖,在換膚時修改它的text_color屬性
    listViewSetter.childViewTextColor(R.id.news_title, R.attr.text_color);


    // 建構Colorful對象
    Colorful mColorful = new Colorful.Builder(this)
        .backgroundDrawable(R.id.root_view, R.attr.root_view_bg) // 設定view的背景圖檔
        .backgroundColor(R.id.change_btn, R.attr.btn_bg) // 設定按鈕的背景色
        .textColor(R.id.textview, R.attr.text_color) // 設定文本顔色
        .setter(listViewSetter)           // 手動設定setter
        .create(); 
           

六、小案例,完整代碼

Android 主題Theme樣式一鍵換膚,非常簡單(附小案例)導語

首先,請将前面代碼複制到項目中。基本上已經完成了百分之80了。就差一個Toolbar和popwindow。

activity_main.xml      
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical"
    tools:context="com.example.aiyang.stickydecoration.MainActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/main_toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/mainColor"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="?attr/colorAccent"
            android:text="Material design" />

        <View
            android:id="@+id/menu_pop"
            android:layout_width="1dp"
            android:layout_height="1dp"
            android:layout_gravity="right"
            android:layout_marginRight="10dp" />
    </android.support.v7.widget.Toolbar>

    <Button
        android:id="@+id/golistpage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:background="?attr/mainBgColor"
        android:text="清單上下拉加載和分頁加載"
        android:textColor="?attr/colorAccent" />

    <Button
        android:id="@+id/coordinatorpage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:background="?attr/mainBgColor"
        android:text="頁面關聯Coordinator"
        android:textColor="?attr/colorAccent" />

    <Button
        android:id="@+id/goshelfpage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:background="?attr/mainBgColor"
        android:text="最終合成的仿美團點餐頁面"
        android:textColor="?attr/colorAccent" />
</LinearLayout>
           
pop_window.xml      
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:gravity="center"
    android:paddingLeft="15dp"
    android:background="@mipmap/zidongfendan_bg">


    <LinearLayout
        android:id="@+id/checkyellow"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical">
        <View
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_weight="0.3"
            android:background="@color/yellow"/>
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="0.7"
            android:orientation="vertical">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="黃色"
                android:layout_margin="10dp"
                android:textSize="18sp"
                android:textColor="@color/txtcolor"/>

            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="@color/backcolor"
                android:layout_marginLeft="8dp"
                />
        </LinearLayout>

    </LinearLayout>


    <LinearLayout
        android:id="@+id/checkblue"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical">

        <View
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_weight="0.3"
            android:background="@color/blue"/>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="0.7"
            android:orientation="vertical">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="藍色"
                android:layout_margin="10dp"
                android:textSize="18sp"
                android:textColor="@color/txtcolor"/>

            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="@color/backcolor"
                android:layout_marginLeft="8dp"
                />
        </LinearLayout>

    </LinearLayout>


    <LinearLayout
        android:id="@+id/checkgrey"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical">

        <View
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_weight="0.3"
           android:background="@color/grey"/>

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="0.7"
            android:orientation="vertical">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="淺灰"
                android:layout_margin="10dp"
                android:textSize="18sp"
                android:textColor="@color/txtcolor"/>

            <View
                android:layout_width="match_parent"
                android:layout_height="1dp"
                android:background="@color/backcolor"
                android:layout_marginLeft="8dp"
                />
        </LinearLayout>

    </LinearLayout>

    <LinearLayout
        android:id="@+id/checkgreen"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_vertical">

        <View
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_weight="0.3"
            android:background="@color/green"/>

        <LinearLayout
            android:layout_weight="0.7"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="綠色"
            android:layout_margin="10dp"
            android:textSize="18sp"
            android:textColor="@color/txtcolor"/>
        </LinearLayout>

    </LinearLayout>

</LinearLayout>
           
colors.xml

<color name="txtcolor">#000000</color>
<color name="yellow" >#FFEB3B</color>
<color name="blue" >#03A9F4</color>
<color name="green" >#009688</color>
<color name="grey">#607D8B</color>      

mipmap-mdpi檔案下圖檔

Android 主題Theme樣式一鍵換膚,非常簡單(附小案例)導語

MainActivity

public class MainActivity extends AppCompatActivity {
    private MainActivity mContext;
    private PopupWindow popupWindow;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //設定主題
        ThemeUtil.setBaseTheme(this);
        setContentView(R.layout.activity_main);
        mContext = this;
        Toolbar toolbar= (Toolbar) findViewById(R.id.main_toolbar);
        setSupportActionBar(toolbar);
        //設定不現實自帶的title文字
        getSupportActionBar().setDisplayShowTitleEnabled(false);


        findViewById(R.id.golistpage).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(MainActivity.this, ListScrollListenerActivity.class));
            }
        });

        findViewById(R.id.goshelfpage).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startActivity(new Intent(MainActivity.this, GoodsShelfActivity.class));
            }
        });

        findViewById(R.id.coordinatorpage).setOnClickListener(new View.OnClickListener() {
                                                                  @Override
                                                                  public void onClick(View view) {
                                                                      startActivity(new Intent(MainActivity.this, CoordinatorActivity.class
                                                                      ));
                                                                  }
                                                              }
        );
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main,menu);
        return true;
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        menu.findItem(R.id.settings).setIcon(android.support.v7.appcompat.R.drawable.abc_ic_menu_overflow_material);
        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        if (item.getItemId() == R.id.settings){
            showPopupWindow(findViewById(R.id.menu_pop));
        }
        return true;
    }

    /**
     * 更多菜單選項彈框
     *
     * @param view
     */
    private void showPopupWindow(View view) {
        // 一個自定義的布局,作為顯示的内容
        View contentView = LayoutInflater.from(this).inflate(
                R.layout.pop_window, null);

        contentView.findViewById(R.id.checkblue).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ThemeUtil.setNewTheme(mContext, ThemeUtil.ThemeColors.ThEME_BLUE)){
                    recreate();
                }
                popupWindow.dismiss();
            }
        });
        contentView.findViewById(R.id.checkyellow).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ThemeUtil.setNewTheme(mContext, ThemeUtil.ThemeColors.THEME_YELLOW))
                    recreate();
                popupWindow.dismiss();
            }
        });
        contentView.findViewById(R.id.checkgreen).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ThemeUtil.setNewTheme(mContext, ThemeUtil.ThemeColors.THEME_GREEN))
                    recreate();
                popupWindow.dismiss();
            }
        });
        contentView.findViewById(R.id.checkgrey).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ThemeUtil.setNewTheme(mContext, ThemeUtil.ThemeColors.THEME_GREY))
                    recreate();
                popupWindow.dismiss();
            }
        });
        popupWindow = new PopupWindow(contentView,
                ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, true);

        popupWindow.setBackgroundDrawable(new ColorDrawable(0000000000));
        popupWindow.setFocusable(true);
        popupWindow.setOutsideTouchable(true); // 點選外部關閉
        popupWindow.setAnimationStyle(android.R.style.Animation_Dialog);
        // 設定好參數之後再show
        popupWindow.showAsDropDown(view, 0, 30);
    }
}
           

其他方式的主題實作

網易雲音樂開源項目實作方法大概是添加Gradle依賴後,再建立一個擁有不同圖檔和主題風格的項目子子產品,然後切換所謂的主題切換,就能夠根據不同的方法設定,呈現不同風格的界面給使用者,也就是所謂的換膚。

APK主題方案和主題包儲存到SD卡上(墨迹,搜狗實作方式)的方案類似,隻不過是apk壓縮格式,一些資源的引用可以調用系統api。

APK主題方案的基本思路是:在Android中,所有的資源都是基于包的。資源以id進行辨別,在同一個應用中,每個資源都有唯一辨別。但在不同的應用中,可以有相同的id。是以,隻要擷取到了其他應用的Context對象,就可以通過它的getRsources擷取到其綁定的資源對象。然後,就可以使用Resources的getXXX方法擷取字元串、顔色、dimension、圖檔等。 要想擷取其他應用的Context對象,Android已經為我們提供好了接口。那就是:android.content.ContextWrapper.createPackageContext(String packageName, int flags)方法。
try { 
            String remotePackage = "com.your.themepackagename";  
            Context remoteContext = createPackageContext(remotePackage,  
                    CONTEXT_IGNORE_SECURITY);  
            Resources remoteResources = remoteContext.getResources();  
            text.setText(remoteResources.getText(remoteResources.getIdentifier("application_name", "string", remotePackage)));  
            color.setTextColor(remoteResources.getColor(remoteResources.getIdentifier("color_name", "color", remotePackage)));  
            image.setImageDrawable(remoteResources.getDrawable(remoteResources.getIdentifier("ic_icon", "drawable", remotePackage)));  
        } catch (NameNotFoundException e) {  
            e.printStackTrace();  
        }       
           
除了壓縮包,apk包等實作方式,還可以考慮插件實作方式,目的都是更好的解耦,更友善的疊代項目.