带有ViewHolder奇怪行为的Xamarin Android ListView

时间:2016-11-02 08:59:21

标签: android listview xamarin android-viewholder

我遇到了一些看似工作正常的ListView的问题,但是当点击一个项目时,很明显出现了问题。 我对Xamarin和本机应用程序开发很新,所以这可能只是一个简单的新手工作错误。

我不确定是不是因为我在使用ListViews + ViewHolders的方式方面做了一些错误,或者我只是忘记了什么。

也许我的错误是我们Xamarin noobs陷入的一般陷阱,所以也许你们中有些经验丰富的人可以马上告诉我是什么导致了我的问题。

以下是我的情景:

我有一个名为ProjectTask的对象列表。这样的对象本身可以包含类似的列表,但只有1级深。所以TaskObj.Tasks是可能的,但是TaskObj.Tasks [0] .Tasks不是。

所以我希望我的第一个listview显示所有父任务,当点击一个项目时,我切换到显示该任务的子任务的第二个listview。

这似乎一直有效,直到我滚动第一个listview。完成后,列表视图仍然“看起来”正确,但是当我单击某个项目时,它不是正确选择的项目。

如果一个任务有一个 Name 和一个 Description 属性,它们都显示在listview项目上,那么当我点击该项目时,我可以看到它是一个项目使用另一个 Name ,它实际上被发送到处理第二个listview的活动。

有没有人从这个描述中了解到发生了什么?

实际上我不想立刻发布一堆代码,但我还是会这样做,因为我打赌我会被要求提前一点 - 这是使用中的类的简化版本,只是微不足道东西已被删除。

ViewHolder类

    public class ViewHolderProjectTaskExtended : Java.Lang.Object
{
    public Button btnStop { get; set; }
    public Button btnStart { get; set; }
    public TextView tvName { get; set; }
    public CheckBox is_started { get; set; }
    public TextView task_id { get; set; }

    public ViewHolderProjectTaskExtended()
    {
    }
}

ListAdapter类

    public class ProjectTaskExtendedListAdapter : BaseAdapter<ProjectTask>
{
    List<ProjectTask> _items;
    Activity _context;

    public ProjectTaskExtendedListAdapter(Activity context, List<ProjectTask> tasks)
    {
        _items = tasks;
        _context = context;
    }

    public override ProjectTask this[int position]
    {
        get { return _items[position]; }
    }

    public override int Count
    {
        get { return _items.Count; }
    }

    public override long GetItemId(int position)
    {
        return position;
    }

    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        var item = _items[position];
        ViewHolderProjectTaskExtended viewHolder = null;
        View view = convertView;

        if (view != null)
        {
            viewHolder = view.Tag as ViewHolderProjectTaskExtended;
        }

        #region viewHolder doesn't exist
        if (viewHolder == null)
        {
            view = this._context.LayoutInflater.Inflate(Resource.Layout.ListItem_SalesOrderExtended, null);

            viewHolder = new ViewHolderSalesOrderExtended();

            viewHolder.tvName = view.FindViewById<TextView>(Resource.Id.tv_salesorder_text);
            viewHolder.btnStop = view.FindViewById<Button>(Resource.Id.btn_stop_session);
            viewHolder.btnStart = view.FindViewById<Button>(Resource.Id.btn_start_session);

            viewHolder.is_started = view.FindViewById<CheckBox>(Resource.Id.chb_is_started);
            viewHolder.task_id = view.FindViewById<TextView>(Resource.Id.tv_task_id);

            view.Tag = viewHolder;

            viewHolder.tvName.Text = "(" + item.name + ")" + Environment.NewLine + item.description;

            viewHolder.task_id.Text = item.id;

            viewHolder.btnStart.Tag = item.id;
            viewHolder.btnStop.Tag = item.id;

            if (item.tasks.Count > 0) // has sub tasks
            {
                viewHolder.has_children.Checked = true;
                viewHolder.btnStop.Visibility = ViewStates.Gone;
                viewHolder.btnStart.Visibility = ViewStates.Invisible;

                viewHolder.tvName.Click += (sender, e) =>
                {
                    Toast.MakeText(_context, "Select sub task for " + item.name + "", ToastLength.Short).Show();

                    var ident_select_sub_task = new Intent(_context, typeof(SelectSubTaskActivity));

                    ident_select_sub_task.PutExtra("pt_parent", JsonConvert.SerializeObject(item));

                    _context.StartActivity(ident_select_sub_task);
                };
            }
            else // has no sub tasks
            {
                if (viewHolder.is_started.Checked == false)
                {
                    viewHolder.btnStart.Visibility = ViewStates.Visible;
                    viewHolder.btnStop.Visibility = ViewStates.Gone;
                }
                else
                {
                    viewHolder.btnStart.Visibility = ViewStates.Gone;
                    viewHolder.btnStop.Visibility = ViewStates.Visible;
                }

                viewHolder.btnStart.Click += (sender, e) =>
                {
                    Toast.MakeText(_context, "Task " + item.name + " is starting", ToastLength.Short).Show();

                    // code dealing with starting a task
                };

                viewHolder.btnStop.Click += (sender, e) =>
                {
                    Toast.MakeText(_context, "Task " + item.name + " is stopping", ToastLength.Short).Show();

                    // code dealing with stopping a task
                };
            }
        }
        #endregion

        #region viewHolder exists (reuse)
        else
        {
            viewHolder.tvName.Text = "(" + item.name + ")" + Environment.NewLine + item.description;

            viewHolder.task_id.Text = item.id;

            viewHolder.btnStart.Tag = item.id;
            viewHolder.btnStop.Tag = item.id;

            if (item.tasks.Count > 0) // has sub tasks
            {
                viewHolder.btnStart.Visibility = ViewStates.Invisible;
                viewHolder.btnStop.Visibility = ViewStates.Gone;
            }
            else // has no sub tasks
            {
                if (viewHolder.is_started.Checked == false)
                {
                    viewHolder.btnStart.Visibility = ViewStates.Visible;
                    viewHolder.btnStop.Visibility = ViewStates.Gone;
                }
                else
                {
                    viewHolder.btnStart.Visibility = ViewStates.Gone;
                    viewHolder.btnStop.Visibility = ViewStates.Visible;
                }
            }
        }
        #endregion

        return view;
    }
}

修改

好的,我现在尝试更改我的ListAdapter以跟随你的示例InitLipton,它似乎在这样做的时候工作。 如果我传递标签中的实际项目而不是将索引传递给项目并通过索引检索该项目,我只是不明白为什么它会失败 - 当列表视图滚动时,这会导致出错的机制是什么?

再次更新了ListViewAdapter类(取2),删除了不必要的内容或提高了可读性。

public class ProjectTaskExtendedListAdapter : BaseAdapter<ProjectTask>
{
    List<ProjectTask> _items;
    Activity _context;

    public ProjectTaskExtendedListAdapter(Activity context, List<ProjectTask> tasks)
    {
        _items = tasks;
        _context = context;
    }

    public override ProjectTask this[int position]
    {
        get { return _items[position]; }
    }

    public override int Count
    {
        get { return _items.Count; }
    }

    public override long GetItemId(int position)
    {
        return position;
    }

    public override View GetView(int position, View convertView, ViewGroup parent)
    {
        ViewHolderProjectTaskExtended viewHolder = null;
        View view = convertView;

        if (viewHolder == null)
        {
            view = this._context.LayoutInflater.Inflate(Resource.Layout.ListItem_SalesOrderExtended, null);

            viewHolder = new ViewHolderSalesOrderExtended();

            viewHolder.tvName = view.FindViewById<TextView>(Resource.Id.tv_salesorder_text);
            viewHolder.btnStop = view.FindViewById<Button>(Resource.Id.btn_stop_session);
            viewHolder.btnStart = view.FindViewById<Button>(Resource.Id.btn_start_session);

            viewHolder.is_started = view.FindViewById<CheckBox>(Resource.Id.chb_is_started);
            viewHolder.has_children = view.FindViewById<CheckBox>(Resource.Id.chb_has_children);
            viewHolder.task_id = view.FindViewById<TextView>(Resource.Id.tv_task_id);

            viewHolder.tvName.Click += (sender, e) => itemClicked(viewHolder.tvName);               
        }
        else
        {
            viewHolder = (ViewHolderProjectTaskExtended)view.Tag;
        }

        var item = _items[position];

        viewHolder.tvName.Text = "(" + item.name + ")" + Environment.NewLine + item.description;
        viewHolder.tvName.Tag = position;

        if (item.tasks.Count > 0)
        {
            viewHolder.btnStart.Visibility = ViewStates.Invisible;
            viewHolder.btnStop.Visibility = ViewStates.Gone;
        }
        else
        {
            if (viewHolder.is_started.Checked == false)
            {
                viewHolder.btnStart.Visibility = ViewStates.Visible;
                viewHolder.btnStop.Visibility = ViewStates.Gone;
            }
            else
            {
                viewHolder.btnStart.Visibility = ViewStates.Gone;
                viewHolder.btnStop.Visibility = ViewStates.Visible;
            }
        }

        return view;
    }

    private void itemClicked(object sender)
    {
        var tv = sender as TextView;

        var position = (int)tv.Tag;
        var _item = _items[position];

        Toast.MakeText(_context, "Select sub task for " + _item.name + "", ToastLength.Short).Show();

        var ident_select_sub_task = new Intent(_context, typeof(SelectSubTaskActivity));

        ident_select_sub_task.PutExtra("pt_parent", JsonConvert.SerializeObject(_item));

        _context.StartActivity(ident_select_sub_task);
    }
}

1 个答案:

答案 0 :(得分:1)

对于您正在创建的Textview的标记,请使用Item的位置。然后,您将能够将其用作项目列表中的索引

这是我之前做的一个适配器,但请看CheckBox。当它作为obj进入SetChecked时,我可以将其解析回一个复选框,然后我得到Tag inv,它是列表中项目的位置。

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

        if (convertView == null)
        {
            convertView = _activity.LayoutInflater.Inflate(Resource.Layout.CarItem, parent, false);

            holder = new ViewHolder
            {
                CheckBox = convertView.FindViewById<CheckBox>(Resource.Id.CheckBoxActiveItem),
                Title = convertView.FindViewById<TextView>(Resource.Id.Title),
            };

            convertView.Tag = holder;
            convertView.SetTag(Resource.Id.CheckBoxActiveItem, holder.CheckBox);
            convertView.SetTag(Resource.Id.Title, holder.Title);
        }
        else
        {
            holder = (ViewHolder)convertView.Tag;
        }


        var item = _items[position];
        holder.Title.Text = item .DisplayName;
        holder.CheckBox.Checked = item .IsDefault;
        holder.CheckBox.Click += (sender, args) => SetChecked(holder.CheckBox.Checked, sender);
        holder.CheckBox.Tag = position;


        return convertView;
    }


     private void SetChecked(bool isChecked, object sender)
    {

        var box = sender as CheckBox;

        //Now you have the Item that has been selected, regardless of the scroll
        var position = (int)box.Tag;
        var ccItem = _items[position];
    }