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

Xamarin安卓自定义ListView实现多卖家商品选购问题问询

Xamarin Android 双卖家商品列表:最佳实现与问题修复

嘿,我来帮你搞定这个双卖家商品订购的ListView问题!你遇到的累加异常、数据错乱还有性能顾虑,本质上都是没正确处理ListView的View复用机制和数据集与UI的同步逻辑。下面是我整理的最佳实现方案,完全不用第三方库,还能解决你所有的问题:

核心问题拆解

先理清楚你当前代码的问题根源:

  1. 点击累加异常:每次GetView()都重新绑定Click事件,复用View时旧的事件没被移除,导致一次点击触发多次回调,累加值越来越大。
  2. 滚动数据错乱:没有用ViewHolder保存控件引用,每次都重新Inflate或者查找控件,加上数据集和UI不同步,滚动时就会出现数据串位。
  3. 性能浪费:强制Inflate视图会重复创建View对象,每次点击都查数据库全表,这些都是完全可以优化的点。

优化后的完整实现方案

1. 用ViewHolder模式搞定View复用

ViewHolder是ListView优化的标准操作,它能帮你缓存每行的控件引用,避免重复调用FindViewById,同时解决事件重复绑定的问题。我们先在Adapter里定义一个ViewHolder类:

2. 修正点击事件绑定逻辑

要确保每个控件的点击事件只绑定一次,并且通过ViewHolder关联当前行的数据,这样就不会因为View复用导致position错乱。

3. 优化数据集与UI同步

  • 不要每次点击都全量查询数据库,直接更新内存里的数据集,然后只刷新对应的行就行。
  • 替换全量刷新的updateAdapter(),改用更高效的局部刷新方法。

完整优化后的Adapter代码

class MyListViewAdapterQuotes : BaseAdapter<dbQuotes> {
    private List<dbQuotes> mItems;
    private readonly Context mContext;
    private readonly int mRowLayout;
    private readonly string mdbPath;

    // ViewHolder类:缓存每行控件,关联当前行数据
    private class ViewHolder : Java.Lang.Object {
        public TextView txtBuy_One { get; set; }
        public TextView txtBuy_Two { get; set; }
        public TextView txtQ_One { get; set; }
        public TextView txtQ_Two { get; set; }
        public dbQuotes CurrentQuote { get; set; } // 绑定当前行的商品数据
    }

    // 构造函数
    public MyListViewAdapterQuotes(Context context, List<dbQuotes> items, int rowLayout, string dbPath) {
        mItems = items;
        mContext = context;
        mRowLayout = rowLayout;
        mdbPath = dbPath;
    }

    // 局部更新指定行的数据(替代全量查询)
    public void UpdateItem(int position, dbQuotes updatedQuote) {
        if (position >= 0 && position < mItems.Count) {
            mItems[position] = updatedQuote;
            NotifyItemChanged(position); // 只刷新对应行,性能更优
        }
    }

    // 全量刷新数据集(仅在整体数据变化时使用,比如初始化)
    public void RefreshFullDataSet() {
        mItems.Clear();
        mItems.AddRange(helpers.getQuotes(mdbPath));
        NotifyDataSetChanged();
    }

    // 实现BaseAdapter的必要属性
    public override int Count => mItems.Count;
    public override long GetItemId(int position) => position;
    public override dbQuotes this[int position] => mItems[position];

    public override View GetView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        View row = convertView;

        if (row == null) {
            // 仅在首次创建行视图时Inflate
            row = LayoutInflater.From(mContext).Inflate(mRowLayout, parent, false);
            
            // 初始化ViewHolder并绑定控件
            holder = new ViewHolder();
            holder.txtBuy_One = row.FindViewById<TextView>(Resource.Id.Buy_One);
            holder.txtBuy_Two = row.FindViewById<TextView>(Resource.Id.Buy_Two);
            holder.txtQ_One = row.FindViewById<TextView>(Resource.Id.q_One);
            holder.txtQ_Two = row.FindViewById<TextView>(Resource.Id.q_Two);
            
            // 将ViewHolder绑定到View的Tag,方便复用
            row.Tag = holder;

            // 只绑定一次点击事件,避免重复绑定
            holder.txtBuy_One.Click += OnBuyOneClicked;
            holder.txtBuy_Two.Click += OnBuyTwoClicked;
        } else {
            // 复用已有的ViewHolder
            holder = row.Tag as ViewHolder;
        }

        // 将当前行的数据关联到ViewHolder
        holder.CurrentQuote = mItems[position];
        // 更新UI显示
        UpdateRowUI(holder);

        return row;
    }

    // 更新ViewHolder对应的UI内容
    private void UpdateRowUI(ViewHolder holder) {
        var quote = holder.CurrentQuote;
        holder.txtBuy_One.Text = quote.One.ToString("0.#");
        holder.txtBuy_Two.Text = quote.Two.ToString("0.#");
        holder.txtQ_One.Text = quote.Q_One.ToString();
        holder.txtQ_Two.Text = quote.Q_Two.ToString();
    }

    // 卖家1的点击事件处理
    private void OnBuyOneClicked(object sender, EventArgs e) {
        var textView = sender as TextView;
        var holder = textView?.Parent.Tag as ViewHolder;
        if (holder?.CurrentQuote == null) return;

        // 用using确保数据库连接及时释放
        using (var db = new SQLiteConnection(mdbPath)) {
            var quote = holder.CurrentQuote;
            quote.Q_One += 1;
            db.Update(quote);
            
            // 更新内存数据集并刷新对应行
            var itemPosition = mItems.IndexOf(quote);
            UpdateItem(itemPosition, quote);
        }
    }

    // 卖家2的点击事件处理
    private void OnBuyTwoClicked(object sender, EventArgs e) {
        var textView = sender as TextView;
        var holder = textView?.Parent.Tag as ViewHolder;
        if (holder?.CurrentQuote == null) return;

        using (var db = new SQLiteConnection(mdbPath)) {
            var quote = holder.CurrentQuote;
            quote.Q_Two += 1;
            db.Update(quote);
            
            // 更新内存数据集并刷新对应行
            var itemPosition = mItems.IndexOf(quote);
            UpdateItem(itemPosition, quote);
        }
    }
}

关键优化点说明

  • ViewHolder模式:彻底解决View复用带来的控件查找和事件重复绑定问题,大幅提升ListView的滚动性能。
  • 事件绑定优化:点击事件只在View首次创建时绑定一次,通过ViewHolder获取当前行的数据,避免position复用导致的错误。
  • 数据集更新优化:
    • 点击后直接更新内存中的mItems,然后调用NotifyItemChanged(position)只刷新对应行,比全量刷新高效得多。
    • 仅在需要同步全量数据时(比如应用启动、卖家商品更新)调用RefreshFullDataSet(),避免每次点击都查询整个数据库。
  • 资源管理:用using语句管理SQLite连接,确保资源及时释放,避免内存泄漏。

额外小建议

  • 如果后续你的商品列表数据量变大,可以考虑改用RecyclerView替代ListView,它的复用机制更高效,还支持更多灵活的布局,但当前方案用ListView已经完全能满足你的需求。
  • 可以把数据库操作放到后台线程执行(比如用Task.Run),避免阻塞UI线程,让点击操作更流畅。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:51:13