天天看點

android app換膚(更換主題)

app換膚主要是更換一下幾點:

1,狀态欄背景更換

2,标題欄背景更換

3,标題欄字型顔色更換

4,界面背景更換

5,界面字型顔色更換

這裡狀态欄更換隻支援android版本号大于20的

方法有兩種:

方法1:

setThem(int resid)設定,缺點是每次設定完都要重新開機該activity,才能應用主題

android項目建立時都會在styles.xml中生成類似以下的代碼

<style name="AppTheme" 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/colorPrimary</item>//特殊醒目的顔色

    <item name="android:windowBackground">@color/windowBg</item>//背景顔色

</style>

android系統有提供Theme設定的方法setTheme(@StyleRes final int resid),對應的調用方法setThem(R.style.AppTheme)

在上邊的基礎上我們就可建多個Theme,然後每個Activity都可以設定成不同的Theme

<activity

    android:name=".MainActivity"

    android:label="@string/app_name"

    android:theme="@style/AppTheme">

我們主要是目的是在一個Activity裡更換多種主題,那麼我們就不在manifest中設定Theme了改為在代碼中設定

public void myTheme(){

SharedPreferences sharedPreferences = context.getSharedPreferences(

        APPTHEME, context.MODE_PRIVATE);

int currentTheme = sharedPreferences.getInt("theme_type", 3);

int theme ;

switch (currrentTheme) {

    case 1:

        theme  = R.style.Theme1

        break;

    case 2:

         theme  = R.style.Theme2

        break;

    case 3:

         theme  = R.style.Theme3

        break;

}

setThem(theme )

}

//選擇Theme時

public void changeTheme(int theme){

SharedPreferences sharedPreferences = context.getSharedPreferences(APPTHEME, context.MODE_PRIVATE);

sharedPreferences.edit().putInt("theme_type",theme).commit();

//重新開機activity

}

方法2:

步驟1:layout檔案把需要改變字型或背景顔色的控件做上标記

步驟2:在Activity繪制ui的時候将有标記的控件全部存儲到記憶體中

步驟3:根據标記的類型改變控件的背景,字型等以達到更換主題的效果

當然也有異曲同工的笨方法:在Activity中把需要更改字型或背景顔色的控件執行個體化,存儲起來,換膚時,逐個改變控件的設定

此笨方法需要在每個需要換膚的Activity中都做重複的工作,而且沒有技術含量,乃程式員之大忌,是以我們繼續我們這種優雅的換膚方法。

關于收集有标記的View,可以通過android 提供的setFactory(LayoutInflater inflater, LayoutInflaterFactory factory),關于該方法,android有以下說明:

大緻意思是說這個方法在系統繪制view時用到的,僅隻能使用一次

既然是在繪制view時用到,那麼view的各種屬性都可在這裡拿到,比如grivity,layout_height等。當然我們自己做的标記也能拿到

public class MyInflaterFactory implements LayoutInflaterFactory {


    public static final String TAG = "MyInflaterFactory";
    public static final String NAMESPACE = "http://schemas.android.com/android/skin";
    public static final String ATTR_SKIN_TYPE = "theme_type";
    public static final String BackGround = "BackGround";
    public static final String TextColor = "TextColor";
    public static final String BackGround_Primary = "BackGround|Primary";
    public static final String ColorAccent = "ColorAccent";
    private Context context;
    private List<View> views = new ArrayList<>();
    int[] colors = {0x03a9f4,0x038dcc,0xffffff,0x84ffff};//colorPrimary,colorPrimaryDark,TextColor,windowBg


    public MyInflaterFactory(){
        updateStatusBar();
    }




    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        this.context = context;
        // 檢測目前View是否有更換皮膚的需求
//        boolean isSkinEnable = attrs.getAttributeBooleanValue(NAMESPACE, ATTR_SKIN_ENABLE, false);
        String changeType = attrs.getAttributeValue(NAMESPACE, ATTR_SKIN_TYPE);
        if (TextUtils.isEmpty(changeType)) {
            return null;//傳回空就使用預設的InflaterFactory
        }
        View view = createView(context,name, attrs);
        if (view == null) {//沒有找到這個View
            return null;
        }
        view.setTag(changeType);
        views.add(view);//将有标記的控件存入數組,以便換膚
        updateItemView(view,colors);//更新預設皮膚
        return view;
    }


    public void setNewTheme(int[] colors){//外部換膚調用
        this.colors = colors;
        for(View view:views){
            updateItemView(view,this.colors);
        }


    }


    public int[] getThemeColors() {
        return colors;
    }


    public int[] updateStatusBar(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
        {
            ((Activity)context).getWindow().setStatusBarColor(colors[1]);//android版本号>=21改變狀态欄


        }
        ((Activity)context).getWindow().setBackgroundDrawable(new ColorDrawable(colors[3]));//改變Activity背景顔色
        return colors;
    }


    private View createView(Context context, String name, AttributeSet attrs) {
        Log.i(TAG, "createView:" + name);
        View view = null;
        try {
            if (-1 == name.indexOf('.')) {
                if ("View".equals(name)) {
                    view = LayoutInflater.from(context).createView(name, "android.view.", attrs);
                }
                if (view == null) {
                    view = LayoutInflater.from(context).createView(name, "android.widget.", attrs);
                }
                if (view == null) {
                    view = LayoutInflater.from(context).createView(name, "android.webkit.", attrs);
                }
            } else {
                view = LayoutInflater.from(context).createView(name, null, attrs);
            }


        } catch (Exception e) {
            Log.e(TAG, "error while create 【" + name + "】 : " + e.getMessage());
            view = null;
        }


        return view;
    }


    @SuppressLint("NewApi")
    private void updateItemView(View v,int[]colors){
        String type_color = (String) v.getTag();
        if(type_color.equals(BackGround)){
            if (v instanceof CardView) {//這裡對CardView特殊處理下
                CardView cardView = (CardView) v;
                cardView.setCardBackgroundColor(colors[1]);
            } else {
                v.setBackgroundColor(colors[1]);
            }


        }
        else if(type_color.equals(TextColor)){
            if(v instanceof TextView){
                ((TextView) v).setTextColor(colors[2]);
            }else if (v instanceof EditText){
                ((EditText) v).setTextColor(colors[2]);
            }


        }
        else if(type_color.equals(BackGround_Primary)){


                v.setBackgroundColor(colors[0]);


        }


        if(type_color.equals(ColorAccent)){
            v.setBackgroundColor(colors[2]);
        }


    }
}
           

Activity onCreate(...)方法中在調用setContentView()之前:

MyInflaterFactory factory = new MyInflaterFactory();

LayoutInflaterCompat.setFactory(getLayoutInflater(), factory);

布局檔案中做标記:

<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"
    xmlns:skin="http://schemas.android.com/android/skin"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="zhao.edifier.com.mynotepaper.MainActivity">


    <RelativeLayout
        android:id="@+id/al_title"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="?attr/colorPrimary"
        skin:theme_type="BackGround|Primary"
        android:elevation="10dp"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:textColor="@android:color/white"
            android:textSize="17sp"
            android:text="@string/app_name"
            android:padding="8dp"
            skin:theme_type="TextColor"
            />


        <ImageView
            android:id="@+id/iv_more"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_alignParentEnd="true"
            android:src="@drawable/ic_more_withe"
            android:padding="16dp"
            android:scaleType="centerInside"
            android:layout_centerVertical="true"
            android:background="@drawable/recycler_bg"
            />


    </RelativeLayout>
</RelativeLayout>
           

根目錄中的xmlns:skin="http://schemas.android.com/android/skin"

和其他skin:theme_type="XX"都是我們自己的标記

XX是 MyInflaterFactory中的

public static final String BackGround = "BackGround";

public static final String TextColor = "TextColor";

public static final String BackGround_Primary = "BackGround|Primary";

public static final String ColorAccent = "ColorAccent";

在點選按鈕換膚時調用factory.setNewTheme(int[] colors);

這種發法可以不用重新開機activity,的缺點是收集不到最新控件的view,比如:FloatingActionButton,和toolbar,遇到這些控件都報異常,暫時隻能以其他常用控件代替之