带复选框的列表视图正在选中多个复选框
Listview with Checkbox is checking more than one checkbox
所以我一直在开发一个带有复选框的列表视图的应用程序,我注意到当我 select 向下滚动后向下滚动第一个项目时,一个项目说列表中的第一个项目整页也有 select 复选框。我有一种感觉,这是由于回收的观点,但我很好奇对此的解决方案。我曾尝试添加一个取景器,但这并没有解决问题。只是想知道我错过了什么。适配器上的 getview 代码如下:
public override Android.Views.View GetView(int position, Android.Views.View convertView, ViewGroup parent)
{
Android.Views.View view = convertView;
if (view == null)
{
view = context.LayoutInflater.Inflate(Resource.Layout.GroupTimeTrackerTemplate, parent, false);
}
tblWorkers item = this[position];
view.FindViewById<TextView>(Resource.Id.UserNameTextView).Text = this[position].nWorkerFirstname + " " + this[position].nWorkerLastname;
view.FindViewById<TextView>(Resource.Id.StatusTextView).Text = this[position].nStatusShort;
view.FindViewById<TextView>(Resource.Id.UserRoleTextView).Text = this[position].nTitle;
CheckBox chkSelect = view.FindViewById<CheckBox>(Resource.Id.ChkSelect);
chkSelect.Tag = item.nWorkerFirstname + " " + item.nWorkerLastname;
chkSelect.SetOnCheckedChangeListener(null);
chkSelect.SetOnCheckedChangeListener(new CheckChangeListener(this.context));
return view;
}
private class CheckChangeListener : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener
{
private Activity activity;
public CheckChangeListener(Activity activity)
{
this.activity = activity;
}
public void OnCheckedChanged(CompoundButton buttonView, bool isChecked)
{
if (isChecked)
{
string name = (string)buttonView.Tag;
string text = string.Format("{0} Checked.", name);
Toast.MakeText(this.activity, text, ToastLength.Short).Show();
}
}
}
虽然我个人没有通过 Xamarin 开发 Android,但我有 Android 知识可以帮助您朝着正确的方向前进。
如您正确所述,ListView 中的行被回收。问题是,当您滚动时,会调用 OnCheckedChange 方法,并且由于它回收了行,因此它在未选择的新行的位置传递,但由于它与先前选择的索引具有相同的索引改变它的价值。
例如,如果您选中索引为 5 的框,然后向下滚动,新行将变为索引为 5 的行,因此它会更改其状态。
这是 Android 上的常见问题。要解决它,首先你需要一个数据结构来跟踪哪些行已经被检查过,方法是将它存储在地图或列表中。
然后在您的 GetView 中确保将复选框状态设置为该位置的内容。
@Ivan Alburquerque 是对的,因为 Android 回收了 list.You 中的视图,应该始终根据您的位置设置 GetView 方法上复选框的值数据源。
为了避免您在这里编写的所有这些重复代码,我建议您使用像 MvvmCross (https://github.com/mvvmcross/mvvmcross) 这样的 MVVM 框架,它可以为您完成所有工作,并且您需要编写的代码会少很多.
玩得开心。
我最终找到了一个解决方案,它有一个 View Holder,可以处理我的动态模型数据。这对我有用,下面是解决方案!三个要点是业务逻辑对象有一个 bool 来保存被检查的项目和视图持有者帮助提高性能,最后因为视图的回收你不能在点击事件中可靠地使用列表 [位置] 所以我用了包装纸!
视图持有人:
private class MyViewHolder : Java.Lang.Object
{
public TextView txtName;
public TextView txtStatus;
public TextView txtTitle;
public CheckBox chkItem;
}
适配器获取视图:
public override Android.Views.View GetView(int position, Android.Views.View convertView, ViewGroup parent)
{
var item = list[position];
Android.Views.View view = convertView;
MyViewHolder holder;
if (convertView == null)
{
holder = new MyViewHolder();
view = context.LayoutInflater.Inflate(Resource.Layout.GroupTimeTrackerTemplate, null);
holder.chkItem = view.FindViewById<CheckBox>(Resource.Id.ChkSelect);
holder.txtName = view.FindViewById<TextView>(Resource.Id.UserNameTextView);
holder.txtStatus = view.FindViewById<TextView>(Resource.Id.StatusTextView);
holder.txtTitle = view.FindViewById<TextView>(Resource.Id.UserRoleTextView);
view.Tag = holder;
}
else
{
holder = view.Tag as MyViewHolder;
}
holder.chkItem.Checked = item.StatusSelected;
if (holder.chkItem.Checked)
{
holder.chkItem.Enabled = false;
}
else
{
holder.chkItem.Enabled = true;
}
holder.chkItem.Tag = new MyWrapper<int>(position);
holder.txtName.Text = item.nWorkerFirstname + " " + item.nWorkerLastname;
holder.txtStatus.Text = item.nStatusShort;
holder.txtTitle.Text = item.nTitle;
holder.chkItem.Click -= HolderChkItemClick;
holder.chkItem.Click += HolderChkItemClick;
if (statoChk[position] == 0)
{
holder.chkItem.Checked = false;
}
else
{
holder.chkItem.Checked = true;
}
return view;
}
复选框、包装器和数组的单击事件:
tblWorkers OrigSel = null;
void HolderChkItemClick(object sender, EventArgs e)
{
var chk = sender as CheckBox;
int pos = ((MyWrapper<int>)chk.Tag).value;
tblWorkers selectedWorker = list[pos];
if (Vm.SelectedWorker == null)
{
OrigSel = selectedWorker;
IntentManager.Instance.OriginalSelectedWorker = OrigSel;
Vm.GroupTimeTrackerStatusType = OrigSel.nStatusShort;
}
Vm.SelectedWorker = selectedWorker;
if (chk.Checked)
{
if (selectedWorker.nStatusShort == OrigSel.nStatusShort)
{
statoChk[pos] = 1;
Vm.GroupTimeList.Add(selectedWorker);
}
else
{
statoChk[pos] = 0;
chk.Checked = false;
Toast.MakeText(context, "Please Select Workers with the Same Time Status. Current Status : " + OrigSel.nStatusShort, ToastLength.Long).Show();
}
}
else
{
statoChk[pos] = 0;
Vm.GroupTimeList.Remove(selectedWorker);
if (Vm.GroupTimeList.Count == 0)
{
Vm.SelectedWorker = null;
OrigSel = null;
IntentManager.Instance.OriginalSelectedWorker = null;
Vm.GroupTimeTrackerStatusType = "";
}
}
}
public class MyWrapper<T> : Java.Lang.Object
{
private T _value;
public MyWrapper(T managedValue)
{
_value = managedValue;
}
public T value { get { return _value; } }
}
int[] statoChk;
public int[] StatoCheck
{
get { return statoChk; }
set { statoChk = value; }
}
所以我一直在开发一个带有复选框的列表视图的应用程序,我注意到当我 select 向下滚动后向下滚动第一个项目时,一个项目说列表中的第一个项目整页也有 select 复选框。我有一种感觉,这是由于回收的观点,但我很好奇对此的解决方案。我曾尝试添加一个取景器,但这并没有解决问题。只是想知道我错过了什么。适配器上的 getview 代码如下:
public override Android.Views.View GetView(int position, Android.Views.View convertView, ViewGroup parent)
{
Android.Views.View view = convertView;
if (view == null)
{
view = context.LayoutInflater.Inflate(Resource.Layout.GroupTimeTrackerTemplate, parent, false);
}
tblWorkers item = this[position];
view.FindViewById<TextView>(Resource.Id.UserNameTextView).Text = this[position].nWorkerFirstname + " " + this[position].nWorkerLastname;
view.FindViewById<TextView>(Resource.Id.StatusTextView).Text = this[position].nStatusShort;
view.FindViewById<TextView>(Resource.Id.UserRoleTextView).Text = this[position].nTitle;
CheckBox chkSelect = view.FindViewById<CheckBox>(Resource.Id.ChkSelect);
chkSelect.Tag = item.nWorkerFirstname + " " + item.nWorkerLastname;
chkSelect.SetOnCheckedChangeListener(null);
chkSelect.SetOnCheckedChangeListener(new CheckChangeListener(this.context));
return view;
}
private class CheckChangeListener : Java.Lang.Object, CompoundButton.IOnCheckedChangeListener
{
private Activity activity;
public CheckChangeListener(Activity activity)
{
this.activity = activity;
}
public void OnCheckedChanged(CompoundButton buttonView, bool isChecked)
{
if (isChecked)
{
string name = (string)buttonView.Tag;
string text = string.Format("{0} Checked.", name);
Toast.MakeText(this.activity, text, ToastLength.Short).Show();
}
}
}
虽然我个人没有通过 Xamarin 开发 Android,但我有 Android 知识可以帮助您朝着正确的方向前进。
如您正确所述,ListView 中的行被回收。问题是,当您滚动时,会调用 OnCheckedChange 方法,并且由于它回收了行,因此它在未选择的新行的位置传递,但由于它与先前选择的索引具有相同的索引改变它的价值。
例如,如果您选中索引为 5 的框,然后向下滚动,新行将变为索引为 5 的行,因此它会更改其状态。
这是 Android 上的常见问题。要解决它,首先你需要一个数据结构来跟踪哪些行已经被检查过,方法是将它存储在地图或列表中。
然后在您的 GetView 中确保将复选框状态设置为该位置的内容。
@Ivan Alburquerque 是对的,因为 Android 回收了 list.You 中的视图,应该始终根据您的位置设置 GetView 方法上复选框的值数据源。 为了避免您在这里编写的所有这些重复代码,我建议您使用像 MvvmCross (https://github.com/mvvmcross/mvvmcross) 这样的 MVVM 框架,它可以为您完成所有工作,并且您需要编写的代码会少很多. 玩得开心。
我最终找到了一个解决方案,它有一个 View Holder,可以处理我的动态模型数据。这对我有用,下面是解决方案!三个要点是业务逻辑对象有一个 bool 来保存被检查的项目和视图持有者帮助提高性能,最后因为视图的回收你不能在点击事件中可靠地使用列表 [位置] 所以我用了包装纸!
视图持有人:
private class MyViewHolder : Java.Lang.Object
{
public TextView txtName;
public TextView txtStatus;
public TextView txtTitle;
public CheckBox chkItem;
}
适配器获取视图:
public override Android.Views.View GetView(int position, Android.Views.View convertView, ViewGroup parent)
{
var item = list[position];
Android.Views.View view = convertView;
MyViewHolder holder;
if (convertView == null)
{
holder = new MyViewHolder();
view = context.LayoutInflater.Inflate(Resource.Layout.GroupTimeTrackerTemplate, null);
holder.chkItem = view.FindViewById<CheckBox>(Resource.Id.ChkSelect);
holder.txtName = view.FindViewById<TextView>(Resource.Id.UserNameTextView);
holder.txtStatus = view.FindViewById<TextView>(Resource.Id.StatusTextView);
holder.txtTitle = view.FindViewById<TextView>(Resource.Id.UserRoleTextView);
view.Tag = holder;
}
else
{
holder = view.Tag as MyViewHolder;
}
holder.chkItem.Checked = item.StatusSelected;
if (holder.chkItem.Checked)
{
holder.chkItem.Enabled = false;
}
else
{
holder.chkItem.Enabled = true;
}
holder.chkItem.Tag = new MyWrapper<int>(position);
holder.txtName.Text = item.nWorkerFirstname + " " + item.nWorkerLastname;
holder.txtStatus.Text = item.nStatusShort;
holder.txtTitle.Text = item.nTitle;
holder.chkItem.Click -= HolderChkItemClick;
holder.chkItem.Click += HolderChkItemClick;
if (statoChk[position] == 0)
{
holder.chkItem.Checked = false;
}
else
{
holder.chkItem.Checked = true;
}
return view;
}
复选框、包装器和数组的单击事件:
tblWorkers OrigSel = null;
void HolderChkItemClick(object sender, EventArgs e)
{
var chk = sender as CheckBox;
int pos = ((MyWrapper<int>)chk.Tag).value;
tblWorkers selectedWorker = list[pos];
if (Vm.SelectedWorker == null)
{
OrigSel = selectedWorker;
IntentManager.Instance.OriginalSelectedWorker = OrigSel;
Vm.GroupTimeTrackerStatusType = OrigSel.nStatusShort;
}
Vm.SelectedWorker = selectedWorker;
if (chk.Checked)
{
if (selectedWorker.nStatusShort == OrigSel.nStatusShort)
{
statoChk[pos] = 1;
Vm.GroupTimeList.Add(selectedWorker);
}
else
{
statoChk[pos] = 0;
chk.Checked = false;
Toast.MakeText(context, "Please Select Workers with the Same Time Status. Current Status : " + OrigSel.nStatusShort, ToastLength.Long).Show();
}
}
else
{
statoChk[pos] = 0;
Vm.GroupTimeList.Remove(selectedWorker);
if (Vm.GroupTimeList.Count == 0)
{
Vm.SelectedWorker = null;
OrigSel = null;
IntentManager.Instance.OriginalSelectedWorker = null;
Vm.GroupTimeTrackerStatusType = "";
}
}
}
public class MyWrapper<T> : Java.Lang.Object
{
private T _value;
public MyWrapper(T managedValue)
{
_value = managedValue;
}
public T value { get { return _value; } }
}
int[] statoChk;
public int[] StatoCheck
{
get { return statoChk; }
set { statoChk = value; }
}