安卓开发---在适配器中使用监听器
根据以下的例子,来学习怎么在适配器中怎么使用监听器。
1.在适配器中使用监听器的目的是什么?
答: 实现组件间的解耦通信,Adapter 只应负责数据绑定和视图创建,不应包含业务逻辑,通过监听器将点击事件"转发"给业务层处理。保障线程安全与生命周期安全,自动在主线程回调(避免UI更新崩溃)。
2.在适配器中使用监听器的顺序是什么?
参考以下案例的步骤
在Adapter里面
①定义监听接口
public interface OnItemClickListener { void onItemClick(int position, KeySetting item); }
②设置监听器:将外部传入的监听器对象保存到适配器成员变量中
private OnItemClickListener onItemClickListener;
public void setOnItemClickListener(OnItemClickListener listener) {
this.onItemClickListener = listener;
}
③触发监听
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));
}
});
在Activity中
//【外部使用监听器】实现接口
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());
});
3.具体的数据流是怎么样流动的?
①当用户点击列表项,直接触发适配器中的holder.itemView.setOnClickListener,立刻向Adapter传递数据onItemClickListener.onItemClick(adapterPosition,keyListInAdapter.get(adapterPosition));。
②适配器自动检查监听器有效性,然后转发数据,就是回调listener.onItemClick()。
③Activity收到数据,根据收到的数据处理UI。
标准流程图
核心组件关系图
MainActivity.java
package com.example.myapplication;import android.content.res.ColorStateList;
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";//UIprivate 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<>();//数据列表@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//初始化initView();initClick();initListDate();// 初始化加载配置loadButtonConfigs();selectEarUI(); // 触发首次UI更新}@Overrideprotected 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.修改按键的UIprivate 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));// 设置适配器,将列表数据绑定到RecyclerViewKeySettingAdapter 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);// 更新UIupdateButtonDisplay(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;}}
}
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;}//返回列表项总数@Overridepublic int getItemCount() {return keyListInAdapter.size();}//布局:通过inflate方法将列表项item布局编译为view对象,返回以这个对象为参数的ViewHolder对象(就是创建viewHolder)@NonNull@Overridepublic 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控件中(就是绑定数据)@Overridepublic 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 -> {//为整个列表项设置点击监听器dint adapterPosition = holder.getAdapterPosition();//获取当前点击项在Adapter中的位置//检查位置是否有效:adapterPosition != RecyclerView.NO_POSITION//检查监听器是否已经设置:onItemClickListener != nullif (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;}//新建viewHolderstatic 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);}}
}