安卓开发---耳机的按键设置的UI实例

效果图

主页面

弹窗的点击事件

选择完成后更新主页UI

项目的系统架构设计图

一般的列表适配器:主页面,主页面UI,列表子项数据类,列表适配器,子项UI

Caches:存储持久化数据

MyApplication:初始化数据

分层架构

  • 应用层​ ​:MainActivity处理UI交互

  • ​业务逻辑层​ ​:Caches管理配置数据

  • ​数据持久层​ ​:SharedPreferences本地存储

  • ​视图层​ ​:RecyclerView+ 自定义对话框

数据流转的例子

// 以用户设置左耳双击的功能值为例:

①MainActivity检测到点击事件

②通过Caches读取当前配置:"LEFT_btn_action_2"

③BottomSheetDialog显示可选功能值的列表

④用户选择"下一首"触发:

  • Caches.saveButtonAction(2, "下一首", true)

  • updateButtonDisplay(2, "下一首")

⑤新配置立即持久化到SharedPreferences

分析关键代码

1.Caches中的存储方法,保证重新进入页面的时候能够保持之前的选项

java 复制代码
//存入功能值
//示例:"LEFT_btn_action_1"  : 左耳第1个按钮的功能值
public void saveButtonAction(int buttonIndex, String actionName, boolean isLeft) {
    String key = (isLeft ? "LEFT_" : "RIGHT_") + BUTTON_ACTIONS_PREFIX + buttonIndex;
    sharedPreferences.edit()
            .putString(key, actionName)
            .apply();
}
//获取功能值,默认为无作用
public String getButtonAction(int buttonIndex, boolean isLeft) {
    String key = (isLeft ? "LEFT_" : "RIGHT_") + BUTTON_ACTIONS_PREFIX + buttonIndex;
    return sharedPreferences.getString(key, DEFAULT_ACTION);
}

SharedPreferences的基本方法

写入

java 复制代码
// 获取编辑器
SharedPreferences.Editor editor = sp.edit();

// 存储不同类型数据
editor.putString("username", "Alice");
editor.putInt("age", 25);
editor.putBoolean("isPremium", true);
editor.putFloat("score", 4.5f);
editor.putLong("timestamp", System.currentTimeMillis());

// 提交更改(同步/异步)
editor.apply(); // 异步(无返回值)
editor.commit(); // 同步(返回boolean表示成功)

读取

java 复制代码
//第一个参数为key,第二个参数为默认值
String username = sp.getString("username", "default");
int age = sp.getInt("age", 0);
boolean isPremium = sp.getBoolean("isPremium", false);
float score = sp.getFloat("score", 0.0f);
long timestamp = sp.getLong("timestamp", -1L);

删除

java 复制代码
editor.remove("username"); // 删除指定键
editor.clear();            // 清空所有数据
editor.apply();

2.用定义的整数来区分按钮

java 复制代码
private int oneclick_1 = 1, doubleclick_2 = 2, threeclick_3 = 3, longpress_4 = 4; //用于区分按钮

3.简化onCreate方法,把获取视图和点击事件写初始化方法分出去

java 复制代码
 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化
        initView();
        initClick();
        initListDate();

    }

4.列表的列表项右边有默认的隐藏图片,当用户点击列表项的时候,就显示出来,并且用户下次打开弹窗,这个隐藏图片依然显示

java 复制代码
在Activity里面
 //初始化选中项,遍历列表找到与缓存匹配的项
        for (int i = 0; i < keyList.size(); i++) {
            if (keyList.get(i).getName().equals(currentAction)) {
                adapter.setSelectedPosition(i);//把选中的位置传给适配器
                break;
            }
        }

在Adapter里面
// 选中状态管理(自动取消旧项选中状态,严格保证唯一选中项)
    public void setSelectedPosition(int position) {
        if (selectedPosition != position) {//检查新旧位置是否不同
            int oldPosition = selectedPosition;//保存旧位置
            selectedPosition = position;//更新当前选中位置
            if (oldPosition != -1) {//如果旧位置有效
                notifyItemChanged(oldPosition);//就刷新旧位置
            }
            notifyItemChanged(selectedPosition);//刷新新项UI
        }
    }

5.在适配器中使用监视器

详情参考以下链接:

安卓开发---在适配器中使用监听器-CSDN博客

实例代码

新建类MyApplication,然后在AndroidManifest.xml里面的application里面加上android:name=".MyApplication",目的是:告诉系统MyApplication是应用的全局入口点,继承自 android.app.Application它会在应用启动时 ​​最先初始化​​(早于任何 Activity/Service),像在这个项目里面,可以初始化Caches。

'
MyApplication:初始化

java 复制代码
package com.example.myapplication;

import android.app.Application;

/*执行初始化操作*/
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();

        Caches.init(this);

    }
}

String.xml:存储文本

java 复制代码
<resources>
    <string name="app_name">My Application</string>
    <string name="disconnected">未连接</string>
    <!-- TODO: Remove or change this placeholder text -->
    <string name="hello_blank_fragment">Hello blank fragment</string>


    <string name="one_click">单击</string>
    <string name="double_click">双击</string>
    <string name="three_click">三连击</string>
    <string name="long_press">长按</string>
    <string name="save">保存</string>
    <string name="play_stop">播放/暂停</string>
    <string name="no_effect">无作用</string>
    <string name="audio_assistant">语音助手</string>
    <string name="previous">上一首</string>
    <string name="next">下一首</string>
    <string name="volume_add">音量加</string>
    <string name="volume_sub">音量减</string>
    <string name="anc_change">ANC切换</string>

</resources>

dialog_key_setting_select.xml:弹窗的UI

java 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@color/white"
    android:padding="16dp">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="请选择"
        android:textSize="16sp"
        android:textStyle="bold"
        android:gravity="center"
        android:padding="8dp"
        android:textColor="@color/black"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_actions"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

key_setting_item.xml:子项UI

java 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="48dp"
    android:padding="12dp">

    <TextView
        android:id="@+id/tv_action"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="选项"
        android:textColor="@color/black"
        android:textSize="15sp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/iv_selected"
        />

    <ImageView
        android:id="@+id/iv_selected"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:src="@mipmap/select_icon"
        android:visibility="gone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

activity_main.xml:主页面UI

java 复制代码
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity"
    android:layout_marginTop="30dp"
    >

    <TextView
        android:id="@+id/tv_earLR"
        android:layout_width="161dp"
        android:layout_height="40dp"
        tools:ignore="MissingConstraints"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:background="@color/gray"
        android:layout_marginTop="222dp"

        />

    <TextView
        android:id="@+id/tv_left_earphone"
        android:layout_width="80.5dp"
        android:layout_height="40dp"
        tools:ignore="MissingConstraints"
        app:layout_constraintStart_toStartOf="@id/tv_earLR"
        app:layout_constraintTop_toTopOf="@id/tv_earLR"
        android:text="左耳"
        android:textColor="@color/white"
        android:gravity="center"
        android:background="@color/blue"
        />
    <TextView
        android:id="@+id/tv_right_earphone"
        android:layout_width="80.5dp"
        android:layout_height="40dp"
        tools:ignore="MissingConstraints"
        app:layout_constraintEnd_toEndOf="@id/tv_earLR"
        app:layout_constraintTop_toTopOf="@id/tv_earLR"
        android:text="右耳"
        android:gravity="center"
        />



    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/cl_click"
        android:layout_width="match_parent"
        android:layout_height="184dp"
        android:background="@color/white"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="342dp"
        android:layout_marginStart="25dp"
        android:layout_marginEnd="25dp"
        >

        <ImageView
            android:id="@+id/iv_one_click"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:src="@mipmap/ic_launcher"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="19.5dp"
            android:layout_marginBottom="158.5dp"
            android:layout_marginStart="23dp"
            />

        <TextView
            android:id="@+id/tv_one_click"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/one_click"
            android:textColor="@color/black"
            android:textSize="15sp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="15dp"
            android:layout_marginStart="49dp"
            android:layout_marginBottom="154dp"
            />

        <TextView
            android:id="@+id/tv_one_click_display"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/play_stop"
            android:textColor="@color/word_gray"
            android:textSize="15sp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginTop="16dp"
            android:layout_marginBottom="155.5dp"
            android:layout_marginEnd="35dp"
            />

        <ImageView
            android:id="@+id/iv_one_forward"
            android:layout_width="100dp"
            android:layout_height="45dp"
            tools:ignore="MissingConstraints"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="16dp"
            android:layout_marginStart="302dp"
            android:layout_marginBottom="154dp"
            android:layout_marginEnd="14.5dp"/>
        <ImageView
            android:layout_width="20dp"
            android:layout_height="25dp"
            android:src="@drawable/baseline_arrow_forward_ios_24"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="16dp"
            android:layout_marginStart="302dp"
            android:layout_marginBottom="154dp"
            android:layout_marginEnd="14.5dp"
            />
        <!-- 分割线1 -->
        <View
            android:id="@+id/divider1"
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:background="@color/word_gray"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginTop="45dp"
            android:layout_marginBottom="138.5dp"
            />
        
        <ImageView
            android:id="@+id/iv_double_click"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:src="@mipmap/ic_launcher"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="65.5dp"
            android:layout_marginBottom="112.5dp"
            android:layout_marginStart="23dp"
            />

        <TextView
            android:id="@+id/tv_double_click"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/double_click"
            android:textColor="@color/black"
            android:textSize="15sp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="61.5dp"
            android:layout_marginStart="48.5dp"
            android:layout_marginBottom="108dp"
            />

        <TextView
            android:id="@+id/tv_double_click_display"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/volume_add"
            android:textColor="@color/word_gray"
            android:textSize="15sp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginTop="62.5dp"
            android:layout_marginBottom="109.5dp"
            android:layout_marginEnd="35dp"
            />

        <ImageView
            android:id="@+id/iv_double_forward"
            android:layout_width="100dp"
            android:layout_height="45dp"
            tools:ignore="MissingConstraints"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="60.5dp"
            android:layout_marginBottom="108dp"
            android:layout_marginEnd="14.5dp"
            />

        <ImageView
            android:layout_width="20dp"
            android:layout_height="25dp"
            android:src="@drawable/baseline_arrow_forward_ios_24"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="60.5dp"
            android:layout_marginBottom="108dp"
            android:layout_marginEnd="14.5dp"
            />
        <!-- 分割线2 -->
        <View
            android:id="@+id/divider2"
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:background="@color/word_gray"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginTop="91dp"
            android:layout_marginBottom="92.5dp"
            />

        <ImageView
            android:id="@+id/iv_three_click"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:src="@mipmap/ic_launcher"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="111.5dp"
            android:layout_marginBottom="66.5dp"
            android:layout_marginStart="23dp"
            />

        <TextView
            android:id="@+id/tv_three_click"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/three_click"
            android:textColor="@color/black"
            android:textSize="15sp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="107dp"
            android:layout_marginStart="48.5dp"
            android:layout_marginBottom="62dp"
            />


        <TextView
            android:id="@+id/tv_three_click_display"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/previous"
            android:textColor="@color/word_gray"
            android:textSize="15sp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginTop="107.5dp"
            android:layout_marginBottom="64.5dp"
            android:layout_marginEnd="35dp"
            />

        <ImageView
            android:id="@+id/iv_three_forward"
            android:layout_width="100dp"
            android:layout_height="45dp"
            tools:ignore="MissingConstraints"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="106.5dp"
            android:layout_marginBottom="62dp"
            android:layout_marginEnd="14.5dp"
            />
        <ImageView
            android:layout_width="20dp"
            android:layout_height="25dp"
            android:src="@drawable/baseline_arrow_forward_ios_24"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="106.5dp"
            android:layout_marginBottom="62dp"
            android:layout_marginEnd="14.5dp"
            />
        <!-- 分割线3 -->
        <View
            android:id="@+id/divider3"
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:background="@color/word_gray"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginTop="137dp"
            android:layout_marginBottom="46.5dp"
            />

        <ImageView
            android:id="@+id/iv_long_press"
            android:layout_width="20dp"
            android:layout_height="20dp"
            android:src="@mipmap/ic_launcher"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="158dp"
            android:layout_marginBottom="21dp"
            android:layout_marginStart="23dp"
            />

        <TextView
            android:id="@+id/tv_long_press"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/long_press"
            android:textColor="@color/black"
            android:textSize="15sp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="153dp"
            android:layout_marginStart="49dp"
            android:layout_marginBottom="16dp"
            />

        <TextView
            android:id="@+id/tv_long_press_display"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/audio_assistant"
            android:textColor="@color/word_gray"
            android:textSize="15sp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            android:layout_marginTop="155dp"
            android:layout_marginBottom="17dp"
            android:layout_marginEnd="35dp"
            />

        <ImageView

            android:layout_width="20dp"
            android:layout_height="25dp"
            android:src="@drawable/baseline_arrow_forward_ios_24"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="152.5dp"
            android:layout_marginBottom="16dp"
            android:layout_marginEnd="14.5dp"
            />

        <ImageView
            android:id="@+id/iv_long_forward"
            android:layout_width="100dp"
            android:layout_height="45dp"
            tools:ignore="MissingConstraints"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_marginTop="152.5dp"
            android:layout_marginBottom="16dp"
            android:layout_marginEnd="14.5dp"
            />

    </androidx.constraintlayout.widget.ConstraintLayout>
    
</androidx.constraintlayout.widget.ConstraintLayout>

Caches:存储持久化数据

java 复制代码
package com.example.myapplication;

import android.content.Context;
import android.content.SharedPreferences;

//基于 SharedPreferences实现的轻量级缓存工具类
public class Caches {

    // 按键常量定义
    private static final String BUTTON_TITLES_PREFIX = "btn_title_";
    private static final String BUTTON_ACTIONS_PREFIX = "btn_action_";
    private static final String DEFAULT_ACTION = "无作用";

    SharedPreferences sharedPreferences;

    private SharedPreferences.Editor editor;

    //单例模式
    private static volatile Caches instance;

    // 初始化方法(必须首先调用)
    public static void init(Context context) {
        if (instance == null) {
            instance = new Caches(context.getApplicationContext());
        }
    }

    // 获取实例(确保已初始化)
    public static Caches getInstance() {
        if (instance == null) {
            throw new IllegalStateException("Caches must be initialized first!");
        }
        return instance;
    }


    //构造函数
    private Caches(Context context) {
        sharedPreferences = context.getSharedPreferences("app_cache", Context.MODE_PRIVATE);
        editor = sharedPreferences.edit();
    }


    //=== 按钮配置方法 ===//
    //保存按钮对应的显示文字,buttonIndex表示按钮编号,title是标题,isLeft判断左右耳
    //示例:"LEFT_btn_title_1"  // 左耳第1个按钮的标题
    public void saveButtonTitle(int buttonIndex, String title, boolean isLeft) {
        String key = (isLeft ? "LEFT_" : "RIGHT_") + BUTTON_TITLES_PREFIX + buttonIndex;
        sharedPreferences.edit()
                .putString(key, title)
                .apply();
    }
    //读取标题(无缓存时返回null)
    public String getButtonTitle(int buttonIndex, boolean isLeft) {
        String key = (isLeft ? "LEFT_" : "RIGHT_") + BUTTON_TITLES_PREFIX + buttonIndex;
        return sharedPreferences.getString(key, null);
    }
    //存入功能值
    //示例:"LEFT_btn_action_1"  // 左耳第1个按钮的功能值
    public void saveButtonAction(int buttonIndex, String actionName, boolean isLeft) {
        String key = (isLeft ? "LEFT_" : "RIGHT_") + BUTTON_ACTIONS_PREFIX + buttonIndex;
        sharedPreferences.edit()
                .putString(key, actionName)
                .apply();
    }
    //获取功能值,默认为无作用


    public String getButtonAction(int buttonIndex, boolean isLeft) {
        String key = (isLeft ? "LEFT_" : "RIGHT_") + BUTTON_ACTIONS_PREFIX + buttonIndex;
        return sharedPreferences.getString(key, DEFAULT_ACTION);
    }

}

KeySetting:列表子项数据类

java 复制代码
package com.example.myapplication;

public class KeySetting {
    private String name;

    public KeySetting(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }


}

KeySettingAdapter:列表适配器

java 复制代码
package com.example.myapplication;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class KeySettingAdapter extends RecyclerView.Adapter<KeySettingAdapter.ViewHolder> {

    //定义数据成员(KeySetting)类型,用于接收Activitiy调用适配器是传递数据,实现数据共享
    private List<KeySetting> keyListInAdapter;//数据源:选项列表
    private int selectedPosition = -1;//当前选中项的位置,-1表示未选中


    //构造函数
    public KeySettingAdapter(List<KeySetting> keyListInAdapter) {
        this.keyListInAdapter = keyListInAdapter;
    }

    // 1.【定义监听接口】添加点击监听器接口
    public interface OnItemClickListener {
        void onItemClick(int position, KeySetting item);
    }
    //2.【设置监听器】将外部传入的监听器对象保存到适配器成员变量中
    private OnItemClickListener onItemClickListener;
    public void setOnItemClickListener(OnItemClickListener listener) {
        this.onItemClickListener = listener;
    }

    //返回列表项总数
    @Override
    public int getItemCount() {
        return keyListInAdapter.size();
    }

    //布局:通过inflate方法将列表项item布局编译为view对象,返回以这个对象为参数的ViewHolder对象(就是创建viewHolder)
    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.key_setting_item, parent, false);
        return new ViewHolder(view);
    }


    //将数据渲染到列表项的ViewHolder的view控件中(就是绑定数据)
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        //给第position条目 绑定数据(从0开始计数) 对应的数据是keyListInAdapter的position个元素
        KeySetting item = keyListInAdapter.get(position);

        holder.tvAction.setText(item.getName());
        //控制选中图标显示还是隐藏,选中就显示,未选择就隐藏
        holder.ivSelected.setVisibility(position == selectedPosition ? View.VISIBLE : View.GONE);

        //设置点击事件
        //当用户点击某个列表项时,触发回调通知外部
        //【触发监听】当列表项的任意区域被点击时
        holder.itemView.setOnClickListener(v -> {//为整个列表项设置点击监听器d
            int adapterPosition = holder.getAdapterPosition();//获取当前点击项在Adapter中的位置
            //检查位置是否有效:adapterPosition != RecyclerView.NO_POSITION
            //检查监听器是否已经设置:onItemClickListener != null
            if (adapterPosition != RecyclerView.NO_POSITION && onItemClickListener != null) {
                //触发回调,传递两个关键数据,点击的位置索引,点击项的数据对象
                onItemClickListener.onItemClick(adapterPosition, keyListInAdapter.get(adapterPosition));
            }
        });
    }

    // 选中状态管理(自动取消旧项选中状态,严格保证唯一选中项)
    public void setSelectedPosition(int position) {
        if (selectedPosition != position) {//检查新旧位置是否不同
            int oldPosition = selectedPosition;//保存旧位置
            selectedPosition = position;//更新当前选中位置
            if (oldPosition != -1) {//如果旧位置有效
                notifyItemChanged(oldPosition);//就刷新旧位置
            }
            notifyItemChanged(selectedPosition);//刷新新项UI
        }
    }

    // 获取当前选中项
    public int getSelectedPosition() {
        return selectedPosition;
    }



    //新建viewHolder
    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView tvAction;
        ImageView ivSelected;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            tvAction = itemView.findViewById(R.id.tv_action);
            ivSelected = itemView.findViewById(R.id.iv_selected);
        }
    }
}

MainActivity.java:主页面

java 复制代码
package com.example.myapplication;

import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.google.android.material.bottomsheet.BottomSheetDialog;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    //UI
    private TextView leftEarphone, rightEarphone; //左耳,右耳
    private ImageView oneClickForward, doubleClickForward, threeClickForward, longPressForward; //单击,双击,三击,长按
    private TextView oneClickWoldDispaly, doubleClickWoldDispaly, threeClickWoldDispaly, longPressWoldDispaly; //显示的功能值
    private int oneclick_1 = 1, doubleclick_2 = 2, threeclick_3 = 3, longpress_4 = 4; //用于区分按钮
    private boolean isLeftSelected = true; // 当前选中的耳朵,默认左耳(true 左耳,false 右耳)

    private BottomSheetDialog dialog; // 当前显示的对话框
    List<KeySetting> keyList = new ArrayList<>();//数据列表

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化
        initView();
        initClick();
        initListDate();

        // 初始化加载配置
        loadButtonConfigs();
        selectEarUI(); // 触发首次UI更新
    }
    
    @Override
    protected void onPause() {
        super.onPause();
        dismissCurrentDialog();
    }

    // 初始化列表数据
    private void initListDate() {
        keyList.add(new KeySetting("无作用"));
        keyList.add(new KeySetting("语音助手"));
        keyList.add(new KeySetting("上一首"));
        keyList.add(new KeySetting("下一首"));
        keyList.add(new KeySetting("音量加"));
        keyList.add(new KeySetting("音量减"));
        keyList.add(new KeySetting("播放/暂停"));
        keyList.add(new KeySetting("ANC切换"));
    }



    //1.获取视图
    private void initView() {
        leftEarphone = findViewById(R.id.tv_left_earphone);
        rightEarphone = findViewById(R.id.tv_right_earphone);

        oneClickForward = findViewById(R.id.iv_one_forward);
        doubleClickForward = findViewById(R.id.iv_double_forward);
        threeClickForward = findViewById(R.id.iv_three_forward);
        longPressForward = findViewById(R.id.iv_long_forward);

        oneClickWoldDispaly = findViewById(R.id.tv_one_click_display);
        doubleClickWoldDispaly = findViewById(R.id.tv_double_click_display);
        threeClickWoldDispaly = findViewById(R.id.tv_three_click_display);
        longPressWoldDispaly = findViewById(R.id.tv_long_press_display);
    }

    //2.点击事件
    private void initClick() {
        // 按钮点击事件
        oneClickForward.setOnClickListener(v -> showSelectDialog(oneclick_1));
        doubleClickForward.setOnClickListener(v -> showSelectDialog(doubleclick_2));
        threeClickForward.setOnClickListener(v -> showSelectDialog(threeclick_3));
        longPressForward.setOnClickListener(v -> showSelectDialog(longpress_4));

        // 左耳点击事件
        leftEarphone.setOnClickListener(view -> {
            isLeftSelected = true;
            selectEarUI();//更新UI
        });

        // 右耳点击事件
        rightEarphone.setOnClickListener(view -> {
            isLeftSelected = false;
            selectEarUI();//更新UI
        });
    }



    // 3.初始化 UI, 从Caches读取4个按钮的功能值,默认显示左耳
    private void loadButtonConfigs() {
        // 加载当前选中耳朵的配置
        updateButtonDisplays(isLeftSelected);

    }
    //读存储,更新用来显示的功能值
    private void updateButtonDisplays(boolean isLeft) {
        oneClickWoldDispaly.setText(
                Caches.getInstance().getButtonAction(oneclick_1, isLeft)
        );
        doubleClickWoldDispaly.setText(
                Caches.getInstance().getButtonAction(doubleclick_2, isLeft)
        );
        threeClickWoldDispaly.setText(
                Caches.getInstance().getButtonAction(threeclick_3, isLeft)
        );
        longPressWoldDispaly.setText(
                Caches.getInstance().getButtonAction(longpress_4, isLeft)
        );
    }

    // 4.修改按键的UI
    private void selectEarUI() {
        Log.d(TAG, "选中耳机: " + (isLeftSelected ? "左耳" : "右耳"));

        leftEarphone.setBackgroundColor(ContextCompat.getColor(this,
                isLeftSelected ? R.color.blue : R.color.gray));
        leftEarphone.setTextColor(ContextCompat.getColor(this,
                isLeftSelected ? R.color.white : R.color.black));

        rightEarphone.setBackgroundColor(ContextCompat.getColor(this,
                !isLeftSelected ? R.color.blue : R.color.gray));
        rightEarphone.setTextColor(ContextCompat.getColor(this,
                !isLeftSelected ? R.color.white : R.color.black));


        // 切换按钮时,重新从caches里面更新按钮的功能值
        updateButtonDisplays(isLeftSelected);
    }


    // 5.弹窗展示
    private void showSelectDialog(int buttonIndex) {
        // 先销毁已有对话框,确保同一时间只存在一个对话框实例
        dismissCurrentDialog();
        //创建新对话框
        dialog = new BottomSheetDialog(this);
        //加载对话框布局
        View view = LayoutInflater.from(this).inflate(R.layout.dialog_key_setting_select, null);
        dialog.setContentView(view);
        //获取子视图
        TextView tvTitle = view.findViewById(R.id.tv_title);//子项的标题
        RecyclerView rv = view.findViewById(R.id.rv_actions);//子项的列表

        // 同步标题文字:从Caches读取当前按键的标题
        String savedTitle = Caches.getInstance().getButtonTitle(buttonIndex, isLeftSelected);
        //同步获取当前对话框设置的功能值
        String currentAction = Caches.getInstance().getButtonAction(buttonIndex, isLeftSelected);
        tvTitle.setText(savedTitle != null ? savedTitle : "请选择");

        //配置适配器
        rv.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));//设置分割线
        rv.setLayoutManager(new LinearLayoutManager(this));


        // 设置适配器,将列表数据绑定到RecyclerView
        KeySettingAdapter adapter = new KeySettingAdapter(keyList);
        rv.setAdapter(adapter);

        //初始化选中项,遍历列表找到与缓存匹配的项
        for (int i = 0; i < keyList.size(); i++) {
            if (keyList.get(i).getName().equals(currentAction)) {
                adapter.setSelectedPosition(i);//把选中的位置传给适配器
                break;
            }
        }

        //设置列表项点击监听器
        //【外部使用监听器】实现接口
        adapter.setOnItemClickListener((position, item) -> {
            // 写入缓存,只写当前耳朵
            Caches.getInstance().saveButtonTitle(buttonIndex, item.getName(), isLeftSelected);
            Caches.getInstance().saveButtonAction(buttonIndex, item.getName(), isLeftSelected);

            // 更新UI
            updateButtonDisplay(buttonIndex, item.getName());
            adapter.setSelectedPosition(position);//用户点击时,更新选中状态
            tvTitle.setText(item.getName());
        });

        dialog.setOnDismissListener(dialog -> {
            dialog = null; // 清除引用
        });

        dialog.show();
    }
    //销毁对话框
    private void dismissCurrentDialog() {
        if (dialog != null && dialog.isShowing()) {
            dialog.dismiss();
            dialog = null;
        }
    }

    // 6.把按钮和标题传进来,就立刻更新按钮的显示,(主页面显示的功能值)
    private void updateButtonDisplay(int buttonIndex, String newTitle) {
        switch (buttonIndex) {
            case 1:
                oneClickWoldDispaly.setText(newTitle);
                break;
            case 2:
                doubleClickWoldDispaly.setText(newTitle);
                break;
            case 3:
                threeClickWoldDispaly.setText(newTitle);
                break;
            case 4:
                longPressWoldDispaly.setText(newTitle);
                break;
        }
    }
}
相关推荐
maki0776 小时前
虚幻版Pico大空间VR入门教程 05 —— 原点坐标和项目优化技巧整理
android·游戏引擎·vr·虚幻·pico·htc vive·大空间
千里马学框架7 小时前
音频焦点学习之AudioFocusRequest.Builder类剖析
android·面试·智能手机·车载系统·音视频·安卓framework开发·audio
fundroid10 小时前
掌握 Compose 性能优化三步法
android·android jetpack
TeleostNaCl11 小时前
如何在 IDEA 中使用 Proguard 自动混淆 Gradle 编译的Java 项目
android·java·经验分享·kotlin·gradle·intellij-idea
旷野说12 小时前
Android Studio Narwhal 3 特性
android·ide·android studio
maki07719 小时前
VR大空间资料 01 —— 常用VR框架对比
android·ue5·游戏引擎·vr·虚幻·pico
大熊猫侯佩1 天前
浪浪山 iOS 奇遇记:给 APP 裹上 Liquid Glass “琉璃罩”(下集)
ui·界面设计·ios 26·液态玻璃·liquid glass·glass effect·glass container
xhBruce1 天前
InputReader与InputDispatcher关系 - android-15.0.0_r23
android·ims
领创工作室1 天前
安卓设备分区作用详解-测试机红米K40
android·java·linux