You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xamarin Android RecyclerView单选按钮状态异常与行错乱问题求助

问题描述

我用C#开发Xamarin Android应用,通过Adapter填充RecyclerView的ViewHolder,该ViewHolder包含若干文本字段与一个单选按钮。需求为:单选按钮被勾选时,该行需移至列表底部,同时将状态变更保存至本地数据库,之后调用Adapter的NotifyDataSetChanged()方法。目前遇到两个问题:

  • 点击单选按钮后,其他行的单选按钮也会显示被选中状态
  • 有时点击单选按钮无法切换其勾选/未勾选状态

以下是Adapter代码:

public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
    if (holder is SelectedShoppingListViewHolder selectedShoppingViewHolder)
    {
        selectedShoppingViewHolder.shoppingListItemId.Text = shoppingListItems[position].Id.ToString();
        if (shoppingListItems[position].IsChecked)
        {
            selectedShoppingViewHolder.radioButton.Checked = true;
        }
        selectedShoppingViewHolder.shoppingListItemNameField.Text = shoppingListItems[position].ShoppingListItemName;
        selectedShoppingViewHolder.shoppingListItemQuantityField.Text = shoppingListItems[position].ShoppingListItemQuantity.ToString();
        selectedShoppingViewHolder.shoppingListMeasurementDropDown.SetSelection(shoppingListItems[position].ShoppingListItemMeasurement);

        selectedShoppingViewHolder.radioButton.Click += (sender, args) =>
        {
            if (shoppingListItems[position].IsChecked)
            {
                selectedShoppingViewHolder.radioButton.Checked = false;
                Database.UpdateShoppingListItemIsChecked(shoppingListItems[position].Id, false);
                shoppingListItems = Database.GetAllShoppingListItemsByShopListId(this.shoppingListId).OrderBy(sli => sli.IsChecked).ToList();
                NotifyDataSetChanged();
            }
            else
            {
                selectedShoppingViewHolder.radioButton.Checked = true;
                Database.UpdateShoppingListItemIsChecked(shoppingListItems[position].Id, true);
                shoppingListItems = Database.GetAllShoppingListItemsByShopListId(this.shoppingListId).OrderBy(sli => sli.IsChecked).ToList();
                NotifyDataSetChanged();
            }
        };

        selectedShoppingViewHolder.deleteRowButton.Click += (sender, args) =>
        {
            Database.DeleteShoppingListItemById(int.Parse(selectedShoppingViewHolder.shoppingListItemId.Text));
            shoppingListItems.RemoveAt(position);
            NotifyItemRemoved(position);
        };
    }
}
问题原因与修复方案

1. 单选按钮状态混乱:ViewHolder复用导致状态残留

RecyclerView的ViewHolder是复用机制,你只在IsChecked为true时设置单选按钮选中,但未处理IsChecked为false的情况,导致复用的ViewHolder保留了之前的选中状态,出现其他行单选按钮误选中的问题。

修复:
直接给单选按钮的Checked属性赋值,覆盖所有状态:

// 替换原来的if判断,简洁且覆盖所有状态
selectedShoppingViewHolder.radioButton.Checked = shoppingListItems[position].IsChecked;

2. 点击事件position捕获错误:Lambda变量捕获导致数据错位

Lambda表达式中捕获的position是外部变量,当ViewHolder被复用时,position的值可能已更新,导致点击时操作错误的数据项,出现状态切换失效。

修复:
绑定点击事件前,创建当前position的局部变量,避免捕获外部变量:

var currentPosition = position;
selectedShoppingViewHolder.radioButton.Click += (sender, args) =>
{
    var targetItem = shoppingListItems[currentPosition];
    // 后续操作基于targetItem和currentPosition
};

3. 数据更新与刷新优化:全量拉取数据效率低且易出错

每次点击都从数据库拉取全量数据并排序,不仅效率低,还可能导致状态同步延迟。可以直接修改内存中数据的状态,手动调整列表顺序后用精准的刷新方法替代NotifyDataSetChanged()。

优化后的点击事件代码:

var currentPosition = position;
selectedShoppingViewHolder.radioButton.Click += (sender, args) =>
{
    var targetItem = shoppingListItems[currentPosition];
    var newCheckedState = !targetItem.IsChecked;
    
    // 更新数据库
    Database.UpdateShoppingListItemIsChecked(targetItem.Id, newCheckedState);
    // 更新内存数据状态
    targetItem.IsChecked = newCheckedState;
    
    // 调整列表位置并通知刷新
    if (newCheckedState)
    {
        // 勾选后移至底部
        shoppingListItems.RemoveAt(currentPosition);
        shoppingListItems.Add(targetItem);
        NotifyItemRemoved(currentPosition);
        NotifyItemInserted(shoppingListItems.Count - 1);
    }
    else
    {
        // 取消勾选后移至顶部(按需求调整排序规则)
        shoppingListItems.RemoveAt(currentPosition);
        shoppingListItems.Insert(0, targetItem);
        NotifyItemRemoved(currentPosition);
        NotifyItemInserted(0);
    }
};

4. 重复绑定点击事件:多次绑定导致逻辑重复触发

每次OnBindViewHolder都会给单选按钮添加新的点击事件,导致同一按钮绑定多个事件,触发时逻辑重复执行,状态混乱。

修复:
在ViewHolder构造函数中绑定点击事件,通过回调传递点击位置给Adapter:

// 修改SelectedShoppingListViewHolder
public class SelectedShoppingListViewHolder : RecyclerView.ViewHolder
{
    public TextView shoppingListItemId;
    public RadioButton radioButton;
    // 其他控件声明...

    public SelectedShoppingListViewHolder(View itemView, Action<int> onRadioClick) : base(itemView)
    {
        shoppingListItemId = itemView.FindViewById<TextView>(Resource.Id.shoppingListItemId);
        radioButton = itemView.FindViewById<RadioButton>(Resource.Id.radioButton);
        // 初始化其他控件...

        // 在构造函数中绑定点击事件
        radioButton.Click += (sender, args) => onRadioClick(AdapterPosition);
    }
}

然后在Adapter的OnCreateViewHolder中传入回调:

public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType)
{
    var itemView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.your_item_layout, parent, false);
    return new SelectedShoppingListViewHolder(itemView, HandleRadioButtonClick);
}

private void HandleRadioButtonClick(int position)
{
    // 这里放置优化后的点击逻辑,和步骤3中的代码一致
    var targetItem = shoppingListItems[position];
    var newCheckedState = !targetItem.IsChecked;
    
    Database.UpdateShoppingListItemIsChecked(targetItem.Id, newCheckedState);
    targetItem.IsChecked = newCheckedState;
    
    if (newCheckedState)
    {
        shoppingListItems.RemoveAt(position);
        shoppingListItems.Add(targetItem);
        NotifyItemRemoved(position);
        NotifyItemInserted(shoppingListItems.Count - 1);
    }
    else
    {
        shoppingListItems.RemoveAt(position);
        shoppingListItems.Insert(0, targetItem);
        NotifyItemRemoved(position);
        NotifyItemInserted(0);
    }
}

内容的提问来源于stack exchange,提问作者Kiril

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 15:39:59