Xamarin安卓自定义ListView实现多卖家商品选购问题问询
Xamarin Android 双卖家商品列表:最佳实现与问题修复
嘿,我来帮你搞定这个双卖家商品订购的ListView问题!你遇到的累加异常、数据错乱还有性能顾虑,本质上都是没正确处理ListView的View复用机制和数据集与UI的同步逻辑。下面是我整理的最佳实现方案,完全不用第三方库,还能解决你所有的问题:
核心问题拆解
先理清楚你当前代码的问题根源:
- 点击累加异常:每次
GetView()都重新绑定Click事件,复用View时旧的事件没被移除,导致一次点击触发多次回调,累加值越来越大。 - 滚动数据错乱:没有用ViewHolder保存控件引用,每次都重新Inflate或者查找控件,加上数据集和UI不同步,滚动时就会出现数据串位。
- 性能浪费:强制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
相关产品推荐
相关产品推荐

