效果图
主页面

弹窗的点击事件

选择完成后更新主页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.在适配器中使用监视器
详情参考以下链接:
实例代码
新建类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;
}
}
}