Android:创建带值列表的Spinner式自定义View及功能实现
嘿,我来给你梳理下这个类似Spinner的带值列表自定义View的回调实现和Data Binding绑定方案,刚好我之前做过类似需求,应该能帮到你~
实现带值列表自定义View的回调与Data Binding绑定方案
一、实现onValueChanged、onEntryChanged回调
要实现这两个回调,核心就是定义接口+管理监听器+触发回调这三步,是自定义View的常规操作:
定义回调接口
在你的SelectionTextView类里,根据需求定义两个独立接口(或者合并成一个包含两个方法的接口,看你的业务关联度):// 值变化回调 public interface OnValueChangedListener { void onValueChanged(String oldValue, String newValue); } // 条目位置变化回调 public interface OnEntryChangedListener { void onEntryChanged(int oldPosition, int newPosition); }添加监听器变量与setter方法
在View内部声明监听器实例,并提供外部设置的入口:private OnValueChangedListener mValueChangedListener; private OnEntryChangedListener mEntryChangedListener; public void setOnValueChangedListener(OnValueChangedListener listener) { mValueChangedListener = listener; } public void setOnEntryChangedListener(OnEntryChangedListener listener) { mEntryChangedListener = listener; }在值/条目变化时触发回调
找到你处理选中状态变化的逻辑位置(比如用户点击切换条目、代码设置新值时),调用监听器的方法:// 处理选中状态变化的核心方法 private void handleSelectionChanged(int oldPos, int newPos, String oldVal, String newVal) { // 更新自身状态 mCurrentPosition = newPos; mCurrentValue = newVal; setText(newVal); // 触发回调(记得判空,避免空指针) if (mEntryChangedListener != null) { mEntryChangedListener.onEntryChanged(oldPos, newPos); } if (mValueChangedListener != null) { mValueChangedListener.onValueChanged(oldVal, newVal); } }
二、实现Android Data Binding的值属性绑定
要让自定义View支持Data Binding,有两种常用方式,选哪种看你的需求复杂度:
方式1:通过View的setter自动绑定
Data Binding会自动识别View中符合命名规范的setter方法(比如setValue()对应app:value属性),所以我们只需要给View添加对应的setter:
// 设置选中值 public void setValue(String value) { // 找到值对应的条目位置 int targetPos = -1; for (int i = 0; i < mEntryList.size(); i++) { if (mEntryList.get(i).equals(value)) { targetPos = i; break; } } // 位置有效且和当前选中不同时,触发变化 if (targetPos != -1 && targetPos != mCurrentPosition) { handleSelectionChanged(mCurrentPosition, targetPos, mCurrentValue, value); invalidate(); } } // 设置选中位置 public void setCurrentPosition(int position) { if (position >= 0 && position < mEntryList.size() && position != mCurrentPosition) { String oldVal = mCurrentValue; String newVal = mEntryList.get(position); handleSelectionChanged(mCurrentPosition, position, oldVal, newVal); invalidate(); } }
之后在布局里就可以直接绑定属性了:
<com.yourpackage.SelectionTextView android:layout_width="wrap_content" android:layout_height="wrap_content" app:value="@{viewModel.selectedValue}" app:currentPosition="@{viewModel.selectedPosition}" />
方式2:自定义@BindingAdapter(支持双向绑定)
如果需要实现双向绑定(View值变化同步到ViewModel),或者处理更复杂的属性逻辑,可以用@BindingAdapter和@InverseBindingAdapter:
import androidx.databinding.BindingAdapter; import androidx.databinding.InverseBindingAdapter; import androidx.databinding.InverseBindingListener; // 单向绑定:ViewModel -> View @BindingAdapter("app:selectedValue") public static void setSelectedValue(SelectionTextView view, String value) { view.setValue(value); } // 反向绑定:获取View当前值 @InverseBindingAdapter(attribute = "app:selectedValue") public static String getSelectedValue(SelectionTextView view) { return view.getCurrentValue(); } // 监听View值变化,触发反向绑定 @BindingAdapter(value = {"app:selectedValueAttrChanged"}, requireAll = false) public static void setSelectedValueListener(SelectionTextView view, final InverseBindingListener listener) { view.setOnValueChangedListener((oldValue, newValue) -> { if (listener != null) { listener.onChange(); } }); }
这样就能实现双向绑定了,布局里用@=语法:
<com.yourpackage.SelectionTextView android:layout_width="wrap_content" android:layout_height="wrap_content" app:selectedValue="@={viewModel.selectedValue}" />
三、完整可运行的SelectionTextView代码
import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; import androidx.annotation.Nullable; import java.util.ArrayList; import java.util.List; public class SelectionTextView extends TextView implements View.OnTouchListener { // 条目列表 private List<String> mEntryList = new ArrayList<>(); // 当前选中位置 private int mCurrentPosition = 0; // 当前选中值 private String mCurrentValue; // 回调监听器 private OnValueChangedListener mValueChangedListener; private OnEntryChangedListener mEntryChangedListener; public SelectionTextView(Context context) { super(context); init(); } public SelectionTextView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(); } public SelectionTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { setOnTouchListener(this); // 默认测试数据,实际使用可通过setEntryList替换 mEntryList.add("选项1"); mEntryList.add("选项2"); mEntryList.add("选项3"); mCurrentValue = mEntryList.get(mCurrentPosition); setText(mCurrentValue); } // 设置条目列表 public void setEntryList(List<String> entryList) { if (entryList != null && !entryList.isEmpty()) { mEntryList.clear(); mEntryList.addAll(entryList); mCurrentPosition = 0; mCurrentValue = mEntryList.get(0); setText(mCurrentValue); } } // 获取当前选中值 public String getCurrentValue() { return mCurrentValue; } // 设置选中值 public void setValue(String value) { int targetPos = -1; for (int i = 0; i < mEntryList.size(); i++) { if (mEntryList.get(i).equals(value)) { targetPos = i; break; } } if (targetPos != -1 && targetPos != mCurrentPosition) { handleSelectionChanged(mCurrentPosition, targetPos, mCurrentValue, value); invalidate(); } } // 获取当前选中位置 public int getCurrentPosition() { return mCurrentPosition; } // 设置选中位置 public void setCurrentPosition(int position) { if (position >= 0 && position < mEntryList.size() && position != mCurrentPosition) { String oldVal = mCurrentValue; String newVal = mEntryList.get(position); handleSelectionChanged(mCurrentPosition, position, oldVal, newVal); invalidate(); } } // 处理选中变化,触发回调 private void handleSelectionChanged(int oldPos, int newPos, String oldVal, String newVal) { mCurrentPosition = newPos; mCurrentValue = newVal; setText(newVal); // 触发回调 if (mEntryChangedListener != null) { mEntryChangedListener.onEntryChanged(oldPos, newPos); } if (mValueChangedListener != null) { mValueChangedListener.onValueChanged(oldVal, newVal); } } // 模拟点击切换条目(实际可改成弹出下拉列表等交互) @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { int nextPos = (mCurrentPosition + 1) % mEntryList.size(); setCurrentPosition(nextPos); return true; } return false; } // 回调接口定义 public interface OnValueChangedListener { void onValueChanged(String oldValue, String newValue); } public interface OnEntryChangedListener { void onEntryChanged(int oldPosition, int newPosition); } // 设置监听器 public void setOnValueChangedListener(OnValueChangedListener listener) { mValueChangedListener = listener; } public void setOnEntryChangedListener(OnEntryChangedListener listener) { mEntryChangedListener = listener; } }
实际项目中,你可以基于这个类,根据自己的UI需求(比如弹出下拉列表、自定义选中样式等)修改交互逻辑,只保留必要的回调和Data Binding绑定逻辑即可。
内容的提问来源于stack exchange,提问作者ads
相关产品推荐
相关产品推荐

