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

Xamarin BaseExpandableListAdapter按钮点击返回错误View问题

Xamarin可展开购物车列表控件异常问题

我在Xamarin中实现了分组可展开的购物车样式列表,每组子项包含「+1」「-1」按钮和显示数量的EditText控件,目前遇到以下问题:

  • 点击「+1」或「-1」按钮时,会更新错误的EditText控件(并非当前点击子项对应的控件)
  • 滚动列表后,EditText的值会变为0,且点击按钮时获取到的Holder对象中,groupPosition和childPosition与实际点击项完全不符(比如点击第2组第2项,却得到groupPosition=0、childPosition=5)
  • 已为列表设置稳定ID,且所有子项ID唯一,但问题仍未解决

核心实现代码

GetChildView方法

public override View GetChildView(int groupPosition, int childPosition, bool isLastChild, View convertView, ViewGroup parent)
{
    Holder<HolderObject> holder;
    var child = (JavaObjectWrapper<MobilePOSSubItem>)GetChild(groupPosition, childPosition);

    if (convertView == null)
    {
        convertView = context.LayoutInflater.Inflate(Resource.Layout.mobilePOSTicketsSubItem, null);
        holder = new Holder<HolderObject>(
            new HolderObject {
                GroupPosition = groupPosition,
                ChildPosition = childPosition,
                Amount = convertView.FindViewById<EditText>(Resource.Id.etAmount),
                Tariff = convertView.FindViewById<TextView>(Resource.Id.tariff),
                Name = convertView.FindViewById<TextView>(Resource.Id.name),
                Rule = convertView.FindViewById<TextView>(Resource.Id.rule),
                IconAllotment = convertView.FindViewById<ImageView>(Resource.Id.iconAllotment),
                AddButton = convertView.FindViewById<ImageButton>(Resource.Id.add),
                RemoveButton = convertView.FindViewById<ImageButton>(Resource.Id.remove)
        });

        convertView.Tag = holder;
        convertView.SetTag(Resource.Id.etAmount, holder.Value.Amount);
        convertView.SetTag(Resource.Id.tariff, holder.Value.Tariff);
        convertView.SetTag(Resource.Id.name, holder.Value.Name);
        convertView.SetTag(Resource.Id.rule, holder.Value.Rule);
        convertView.SetTag(Resource.Id.iconAllotment, holder.Value.IconAllotment);
        convertView.SetTag(Resource.Id.add, holder.Value.AddButton);
        convertView.SetTag(Resource.Id.remove, holder.Value.RemoveButton);
    } else
    {
        holder = (Holder<HolderObject>)convertView.Tag;
        holder.Value.Amount.AfterTextChanged -= EtAmount_TextChanged;
    }

    holder.Value.Amount.SetText(child.Object.Value.ToString(), TextView.BufferType.Editable);
    holder.Value.Amount.SetSelectAllOnFocus(true);

    holder.Value.Amount.AfterTextChanged += EtAmount_TextChanged;
    // 省略其他控件赋值代码
}

EtAmount_TextChanged事件处理

private void EtAmount_TextChanged(object sender, AfterTextChangedEventArgs e)
{
    if (sender.GetType().BaseType == typeof(EditText))
    {
        var etAmount = (EditText)sender;
        var parent = (View) etAmount.Parent.Parent;
        Holder<HolderObject> holder = (Holder<HolderObject>)parent.Tag;
        int value = 0;
        string newValue = e.Editable.ToString();
        try
        {
            if (!string.IsNullOrEmpty(newValue))
            {
                value = Convert.ToUInt16(newValue);
            }
            else
            {
                holder.Value.Amount.Text = "0";
            }

        }
        catch
        {
            holder.Value.Amount.Text = "0";
        }

        items[holder.Value.GroupPosition].SubItems[holder.Value.ChildPosition].Value = value;
        NotifyDataSetChanged();
        //UpdateSelectedItem(child, value);
    }
}

OnClick按钮点击处理

public void OnClick(View v)
{
    HideSoftKeyboard(listView);

    View parent = (View) v.Parent.Parent.Parent;
    HolderObject holder = ((Holder<HolderObject>)parent.Tag).Value;

    var child = (JavaObjectWrapper<MobilePOSSubItem>) GetChild(holder.GroupPosition, holder.ChildPosition);
    if (holder.Amount != null)
    {
        int value;
        if (int.TryParse(holder.Amount.Text, out value))
        {
            if (v.Id.Equals(Resource.Id.add))
            {
                if (value < 299 && (child.Object.ValueMax == null || value < child.Object.ValueMax))
                {
                    value++;
                    holder.Amount.Text = value.ToString();
                }
            }
            else if (v.Id.Equals(Resource.Id.remove))
            {
                if (value > 0 && (child.Object.ValueMin == null || value > child.Object.ValueMin))
                {
                    value--;
                    holder.Amount.Text = value.ToString();
                }
            }
        }
        else
        {
            holder.Amount.Text = "0";
        }
    }
    else
    {
        CultureInfo original = CultureInfo.CurrentCulture;
        NumberFormatInfo noCurrencySymbol = (NumberFormatInfo)original.NumberFormat.Clone();
        noCurrencySymbol.CurrencySymbol = string.Empty;

        var cbArticle = (CheckBox)v;
        child.Object.ArticleList.FirstOrDefault(x => x.ArticleId == cbArticle.Id).Value = (cbArticle.Checked) ? 1 : 0;
        holder.Tariff.Text = string.Format("{0} {1}", child.Object.Currency, child.Object.GetTotalAmount().ToString("C", noCurrencySymbol));
    }
}

稳定ID设置

public override long GetChildId(int groupPosition, int childPosition)
{
    return (long) items[groupPosition].SubItems[childPosition].Idx;
}
public override bool HasStableIds
{
    get
    {
        return true;
    }
}

预期流程

  1. 在GetChildView中,若convertView为null则加载布局,创建包含groupPosition、childPosition及各控件的Holder对象并绑定到convertView的Tag
  2. 为EditText绑定AfterTextChanged事件,为按钮绑定OnClick事件
  3. 用户点击「+1」/「-1」按钮→触发OnClick方法→获取按钮的父View及对应的Holder对象→修改Holder中EditText的值→触发AfterTextChanged事件更新列表子项数据

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 19:02:10