天天看点

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,遇到这些控件都报异常,暂时只能以其他常用控件代替之