问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Android应用开发:如何实现主题切换功能

创作时间:
作者:
@小白创作中心

Android应用开发:如何实现主题切换功能

引用
CSDN
1.
https://blog.csdn.net/usabcd2/article/details/143061841

在Android应用开发中,提供主题切换功能可以增强用户体验,让用户根据个人喜好选择不同的界面风格。本文将详细介绍如何实现这一功能,包括定义主题、创建设置界面和处理用户选择等关键步骤。

前言

在Android应用开发中,如果业务功能已经基本完成,想要让应用更加开放和个性化,可以考虑为用户提供主题切换的功能。虽然网上已有不少相关资料,但本文将介绍一种更加优雅和流畅的实现方式。

要实现主题切换功能,需要完成以下三个主要步骤:

  1. 定义主题内容
  2. 创建设置界面
  3. 处理用户选择并切换主题

定义主题内容

首先需要定义应用中可用的主题。例如,我们可以定义5个主题:

  • 霞光紫
  • 科技蓝
  • 烈焰红
  • 田园绿
  • 宝石灰

在常数类C中定义这些主题名称:

public static final String[] themeNames = {"霞光紫", "科技蓝", "烈焰红", "田园绿", "宝石灰"};

然后在values目录下创建theme.xml文件,定义每个主题的具体样式:

<resources>
    <!-- Base application theme. -->
    <style name="Theme.MyApp" parent="Theme.MaterialComponents.DayNight.DarkActionBar.Bridge">
        <!-- Primary brand color. -->
        <item name="colorPrimary">@color/purple_500</item><!--original #3333FF-->
        <item name="colorPrimaryVariant">@color/purple_700</item><!--#00008B-->
        <item name="colorDim">#a569bd</item>
        <item name="colorOnPrimary">@color/white</item>
        <!-- Secondary brand color. -->
        <item name="colorSecondary">@color/teal_200</item>
        <item name="colorSecondaryVariant">@color/teal_700</item>
        <item name="colorOnSecondary">@color/black</item>
        <!-- Status bar color. -->
        <item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
    </style>
    <style name="Theme.MyApp.Blue" parent="Theme.MyApp">
        <item name="colorPrimary">#1565c0</item>
        <item name="colorPrimaryVariant">#1a237e</item>
        <item name="colorDim">#5dade2</item>
    </style>
    <style name="Theme.MyApp.Red" parent="Theme.MyApp">
        <item name="colorPrimary">#bb4444</item>
        <item name="colorPrimaryVariant">#660000</item>
        <item name="colorDim">#ec7063</item>
    </style>
    <style name="Theme.MyApp.Green" parent="Theme.MyApp">
        <item name="colorPrimary">#1e8449</item>
        <item name="colorPrimaryVariant">#145a32</item>
        <item name="colorDim">#52be80</item>
    </style>
    <style name="Theme.MyApp.Gray" parent="Theme.MyApp">
        <item name="colorPrimary">#888888</item>
        <item name="colorPrimaryVariant">#424242</item>
        <item name="colorDim">#a6acaf</item>
    </style>
</resources>

同时在常数类C中定义主题值数组:

public static final Integer[] themeValues = {R.style.Theme_MyApp,
        R.style.Theme_MyApp_Blue,
        R.style.Theme_MyApp_Red,
        R.style.Theme_MyApp_Green,
        R.style.Theme_MyApp_Gray
};

定义设置界面

接下来需要创建一个设置界面,允许用户选择不同的主题。在setting.xml中定义界面布局:

<?xml version="1.0" encoding="utf-8"?>
<layout 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">
    <LinearLayout
        style="?android:attr/buttonBarStyle"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginStart="10dp"
        android:layout_marginEnd="10dp"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:paddingStart="10dp"
        android:paddingTop="30dp"
        android:paddingEnd="10dp"
        android:paddingBottom="0dp">
        <TextView
            android:id="@+id/title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="80dp"
            android:text="@string/system_setting"
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <View
            android:id="@+id/top_line"
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:layout_marginStart="10dp"
            android:layout_marginTop="10dp"
            android:layout_marginEnd="10dp"
            android:background="@color/divider"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/title" />
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginEnd="10dp"
            android:layout_marginTop="30dp"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/input_label"
                android:layout_width="100dp"
                android:layout_height="wrap_content"
                android:layout_marginBottom="10dp"
                android:labelFor="@+id/input_content"
                android:text="@string/server_ip" />
            <EditText
                android:id="@+id/input_content"
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:autofillHints=""
                android:background="@drawable/bg_edittext"
                android:gravity="center_vertical"
                android:inputType="text"
                android:paddingStart="10dp"
                android:paddingEnd="10dp"
                android:textSize="16sp"
                tools:ignore="RtlHardcoded,RtlSymmetry,TextFields" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="10dp"
            android:layout_marginEnd="10dp"
            android:layout_marginTop="30dp"
            android:gravity="center_vertical"
            android:orientation="horizontal">
            <TextView
                android:layout_width="100dp"
                android:layout_height="wrap_content"
                android:labelFor="@+id/input_content"
                android:text="@string/style" />
            <LinearLayout
                android:id="@+id/dropTheme"
                android:layout_width="match_parent"
                android:layout_height="40dp"
                android:layout_gravity="center_vertical"
                android:background="@drawable/bg_edittext"
                android:clickable="true"
                android:focusable="true"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:paddingStart="5dp"
                android:paddingEnd="5dp">
                <TextView
                    android:id="@+id/themeName"
                    android:layout_width="0dp"
                    android:layout_height="40dp"
                    android:layout_marginStart="5dp"
                    android:layout_marginTop="0dp"
                    android:layout_weight="1"
                    android:autofillHints=""
                    android:background="@null"
                    android:textSize="16sp"
                    android:gravity="center_vertical"
                    android:textColor="?attr/editTextColor"
                    android:textColorHint="#DCDCDC"
                    tools:ignore="NestedWeights" />
                <ImageView
                    android:layout_width="13dp"
                    android:layout_height="10dp"
                    android:layout_marginStart="5dp"
                    android:contentDescription="@string/image"
                    android:gravity="center"
                    android:scaleType="fitXY"
                    android:src="@drawable/icon_expanded"
                    app:tint="@color/expanded" />
            </LinearLayout>
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="50dp"
            android:gravity="center"
            android:orientation="horizontal"
            android:paddingEnd="0dp"
            tools:ignore="RtlHardcoded,RtlSymmetry">
            <com.google.android.material.button.MaterialButton
                android:id="@+id/btnOk"
                style="?android:attr/button"
                android:layout_width="100dp"
                android:layout_height="45dp"
                android:gravity="center"
                android:insetTop="0dp"
                android:insetBottom="0dp"
                android:text="@string/ok"
                android:textColor="@color/white" />
            <com.google.android.material.button.MaterialButton
                android:id="@+id/btnBack"
                style="?android:attr/button"
                android:layout_width="100dp"
                android:layout_height="45dp"
                android:layout_marginStart="30dp"
                android:gravity="center"
                android:insetTop="0dp"
                android:insetBottom="0dp"
                android:text="@string/back"
                android:textColor="@color/white" />
        </LinearLayout>
    </LinearLayout>
</layout>

处理用户选择

SettingActivity中处理用户选择主题的逻辑:

import android.content.Intent;
import android.os.Bundle;
import com.bob.app.common.BaseActivity;
import com.bob.app.databinding.SettingBinding;
import com.bob.app.func.FuncRunInt;
import java.util.Arrays;

public class SettingActivity extends BaseActivity {
    SettingBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = SettingBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        int oldThemeValue = sp.getInt("Theme", C.themeValues[0]);
        binding.inputLabel.setText(R.string.server_ip);
        binding.inputContent.setText(C.serverIp);
        int i = Arrays.asList(C.themeValues).indexOf(oldThemeValue);
        i = Math.max(i, 0);
        binding.themeName.setText(C.themeNames[i]);
        int finalI = i;
        binding.dropTheme.setOnClickListener(v -> {
            FuncRunInt fri = (pos) -> {
                if (pos != finalI) {
                    int newTheme = C.themeValues[pos];
                    editor.putInt("Theme", newTheme);
                    editor.commit();//保存用户选择的主题
                    Intent intent = new Intent();
                    setResult(RESULT_OK, intent);
                    recreate();
                }
            };
            showPopupMenu(v, C.themeNames, fri, 220);
        });
        binding.btnBack.setOnClickListener(v -> goBack());
        binding.btnOk.setOnClickListener(v -> goBack());
    }

    private void goBack() {
        String serverIp = binding.inputContent.getText().toString();
        editor.putString("serverIp", serverIp);
        editor.commit();
        C.serverIp = serverIp;
        C.serverUrl = String.format(C.SERVER_URL, serverIp);
        Intent intent = new Intent();
        setResult(RESULT_OK, intent);
        finish();
    }
}

当用户选择某个主题后,需要保存这个主题标识到硬盘,并切换当前主题。这里使用recreate()方法来实现主题切换,可以给用户提供流畅的体验。

切换主题的具体实现

为了确保每个界面都能正确切换主题,需要在基类BaseActivity中处理主题切换的逻辑:

@SuppressWarnings("unused")
public abstract class BaseActivity extends AppCompatActivity {
    protected SharedPreferences sp;
    protected SharedPreferences.Editor editor;
    protected final Set<String> callingActions = new HashSet<>();
    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        sp = getSharedPreferences(getString(R.string.app_name), MODE_PRIVATE);
        editor = sp.edit();
        processTheme();
        super.onCreate(savedInstanceState);
    }

    private void processTheme() {
        int theme = sp.getInt("Theme", C.themeValues[0]);
        int pos = Arrays.asList(C.themeValues).indexOf(theme);
        if (pos < 0) {
            editor.remove("Theme");
            editor.commit();
            theme = C.themeValues[0];
        }
        setTheme(theme);
    }

    public void showPopupMenu(View anchorView, String[] menuItems, FuncRunInt fri, int widthInDp) {
        LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        @SuppressLint("InflateParams") View popupView = inflater.inflate(R.layout.popup_menu_list, null);
        int widthInPx = (int) (widthInDp * getResources().getDisplayMetrics().density);
        final PopupWindow popupWindow = new PopupWindow(popupView,
                widthInPx,
                ViewGroup.LayoutParams.WRAP_CONTENT,
                true);
        popupWindow.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.popup_background))); // Set background color or drawable
        ListView listView = popupView.findViewById(R.id.listView);
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this, R.layout.list_item_left, menuItems);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener((parent, view, position, id) -> {
            fri.apply(position);
            popupWindow.dismiss();
        });
        popupWindow.setOutsideTouchable(true);
        popupWindow.showAsDropDown(anchorView);
    }
}

需要注意的是,processTheme()方法必须在调用super.onCreate(savedInstanceState)之前执行,否则主题切换将不会生效。

通过以上步骤,我们就可以实现一个完整的主题切换功能。由于具体的切换逻辑放在了基类中,因此其他Activity不需要做任何改动就能支持主题切换。

以下是切换后的效果示例:

科技蓝:

田园绿:

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号