Xamarin ListView切换屏幕外项背景崩溃及状态保留问题咨询
嘿,这个问题我太熟悉了!你遇到的崩溃完全是因为直接操作ListView的子视图踩了坑,而且滚动时状态丢失也是没利用好ListView的视图复用机制导致的。我来帮你一步步彻底解决:
问题根源
你代码里的listView.GetChildAt(index)只能获取当前屏幕可见的item视图,当item滚出屏幕外时,这个方法会返回null,直接调用SetBackgroundResource就会触发空引用异常,这就是崩溃的原因。另外,直接操作子视图的方式也没法在滚动时保持状态,因为ListView会复用视图,滚回来的item会被重新绘制,之前的背景设置会丢失。
正确解决方案:数据驱动UI
ListView的设计核心就是通过Adapter绑定数据与视图,正确的做法是只修改数据状态,让Adapter根据数据自动更新UI。这样既避免空引用,又能在滚动时稳定保持选中状态。
步骤1:让数据类支持属性变化通知
首先确保你的ItemUnit类实现INotifyPropertyChanged接口,这样当IsSelected属性变化时,能通知Adapter刷新对应的item:
public class ItemUnit : INotifyPropertyChanged { private bool _isSelected; public bool IsSelected { get => _isSelected; set { if (_isSelected != value) { _isSelected = value; OnPropertyChanged(nameof(IsSelected)); } } } // 这里添加你的其他属性,比如玩家名称、回合数等 public event PropertyChangedEventHandler PropertyChanged; protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
步骤2:修改Adapter,根据数据状态渲染UI
重构你的ListView Adapter,在GetView方法里根据IsSelected属性设置背景色。这样不管item是否可见,只要数据状态变了,滚动时Adapter会自动更新视图:
public class ItemUnitAdapter : BaseAdapter<ItemUnit> { private readonly Context _context; private readonly List<ItemUnit> _itemList; public ItemUnitAdapter(Context context, List<ItemUnit> itemList) { _context = context; _itemList = itemList; } public override ItemUnit this[int position] => _itemList[position]; public override int Count => _itemList.Count; public override long GetItemId(int position) => position; public override View GetView(int position, View convertView, ViewGroup parent) { // 复用视图,提升性能 View itemView = convertView ?? LayoutInflater.From(_context) .Inflate(Resource.Layout.your_item_layout, parent, false); var currentItem = _itemList[position]; // 根据IsSelected设置背景色 itemView.SetBackgroundResource( currentItem.IsSelected ? Resource.Color.colorAccent : Resource.Color.colorPrimary); // 这里绑定其他UI元素,比如玩家名称文本等 // var playerNameTxt = itemView.FindViewById<TextView>(Resource.Id.playerNameTxt); // playerNameTxt.Text = currentItem.PlayerName; return itemView; } }
步骤3:重构按钮点击事件,只修改数据
现在把按钮点击逻辑改成只操作数据,然后通知Adapter刷新,彻底抛弃直接操作子视图的写法:
Button _rotateButton = FindViewById<Button>(Resource.Id.rotateButton); int rotatePrevious = -1; int rotateCurrent = 0; // 初始化Adapter并绑定到ListView var itemAdapter = new ItemUnitAdapter(this, ItemUnits); listView.Adapter = itemAdapter; _rotateButton.Click += delegate { int rotateCount = ItemUnits.Count; if (rotateCount == 0) return; if (rotateCurrent == 0) { _rotateButton.Text = "End Turn"; } // 重置上一个选中的item if (rotatePrevious != -1 && rotatePrevious < rotateCount) { ItemUnits[rotatePrevious].IsSelected = false; } if (rotateCurrent == rotateCount) { // 重置所有item的选中状态 foreach(ItemUnit iu in ItemUnits) { iu.IsSelected = false; } rotateCurrent = 0; rotatePrevious = -1; _rotateButton.Text = "New Round"; } else { // 设置当前回合的item为选中状态 ItemUnits[rotateCurrent].IsSelected = true; rotateCurrent++; rotatePrevious = rotateCurrent - 1; if (rotateCurrent == rotateCount) { _rotateButton.Text = "End Round"; } } // 通知Adapter数据变化,刷新UI itemAdapter.NotifyDataSetChanged(); };
关于ListView的支持情况
完全支持!这种数据驱动UI的方式正是ListView设计的核心逻辑,利用Adapter的视图复用机制,既能避免空引用崩溃,又能在滚动时完美保持选中状态——不管item滚到屏幕外再滚回来,Adapter都会根据最新的IsSelected状态重新渲染背景色。
内容的提问来源于stack exchange,提问作者Mada

