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(); 

六、小案例,完整代码

首先,请将前面代码复制到项目中。基本上已经完成了百分之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文件下图片

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包等实现方式,还可以考虑插件实现方式,目的都是更好的解耦,更方便的迭代项目.

 

猜你喜欢

转载自blog.csdn.net/csdn_aiyang/article/details/78400997
今日推荐