安卓开发---耳机的按键设置的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;
        }
    }
}
相关推荐
gfdgd xi12 小时前
Wine运行器3.4.0——虚拟机安装工具支持设置UEFI启动
android·windows·python·ubuntu·架构
shaominjin12312 小时前
OpenCV 4.1.2 SDK 静态库作用与功能详解
android·c++·人工智能·opencv·计算机视觉·中间件
东坡肘子13 小时前
Swift 官方发布 Android SDK | 肘子的 Swift 周报 #0108
android·swiftui·swift
Storm-Shadow20 小时前
Android OpenGLES视频剪辑示例源码
android·opengles·视频滤镜
双桥wow20 小时前
android 堆栈打印
android
Hi202402171 天前
Qt+Qml客户端和Python服务端的网络通信原型
开发语言·python·qt·ui·网络通信·qml
爱学习的大牛1231 天前
使用C++开发Android .so库的优势与实践指南
android·.so·1024程序员节
帅锅锅0071 天前
SeLinux Type(类型)深度解析
android·操作系统
2501_915921431 天前
iOS混淆与IPA加固全流程(iOS混淆 IPA加固 Ipa Guard实战)
android·ios·小程序·https·uni-app·iphone·webview