Monodroid - 处理重用 ListView 行的子视图上的事件

Posted

技术标签:

【中文标题】Monodroid - 处理重用 ListView 行的子视图上的事件【英文标题】:Monodroid - Handling events on child Views of reused ListView rows 【发布时间】:2015-04-16 12:58:39 【问题描述】:

android 的 ListView 会重复使用已滚动到视图之外的行。 但是,在 C# 中处理行的子视图上的事件时,这似乎是一个问题。

在 Java 中添加事件处理程序的一种公认方法是显式设置一个处理程序,如下所示:

ImageView img = (ImageView) row.findViewById(R.id.pic);
img.setOnClickListener(new View.OnClickListener()   
    public void onClick(View v) 
        System.out.println(position);
      
);

Xamarin 网站上的文档鼓励开发人员使用 C# 的 add 事件侦听器模式,该模式不适合重用的行:

ImageView img = row.FindViewById<ImageView> (Resource.Id.pic);
img.Click += (sender, e) => 
    Console.WriteLine(position);
;

设置事件处理程序的 Java 模式非常适合行重用,而其下方添加事件处理程序的 C# 模式会导致处理程序堆积在子节点上重用行的视图。

下面的代码显示了我编写的自定义 BaseAdapter 中的 GetView 方法。

public override Android.Views.View GetView (int position,
                                            View convertView, ViewGroup parent)

    View row = convertView;

    //TODO: solve event listener bug. (reused rows retain events).
    if (row == null) 
        row = LayoutInflater.From (userListContext)
                .Inflate (Resource.Layout.UserListUser, null, false);
    

    ImageView profilePic = row.FindViewById<ImageView> (Resource.Id.profilePic);

    //if(profilePic.Clickable)  /** kill click handlers? **/ 
    profilePic.Click += async (object sender, EventArgs e) => 
        Bundle extras = new Bundle();
        extras.PutString("id", UserList[position].id);

        Intent intent = new Intent(userListContext, typeof(ProfileActivity));
        intent.PutExtras(extras);
        postListContext.StartActivity(intent);
    ;

    return row;

问题是,当重复使用一行时,profilePic 视图仍然附加了原始的“点击”处理程序。

有没有办法 (a) 清除 profilePic.Click 或 (b) 使用带有匿名函数的 Android 的 profilePic.SetOnClickListener Java 模式?

或者,在“点击”处理程序仍然可以访问 position 的正确值的情况下,是否有更好的模式可以使用?

【问题讨论】:

【参考方案1】:

或者,在“点击”处理程序可以使用的地方是否有更好的模式? 仍然可以访问正确的位置值吗?

使用setTag/getTag方法在ImageView点击监听器的Click方法中获取点击行的正确位置:

profilePic.SetTag(Resource.Id.profilePic, position);
profilePic.Click += async (object sender, EventArgs e) => 
        int clickedPos = (int)(((Button)sender).GetTag (Resource.Id.profilePic));
        Bundle extras = new Bundle();
        extras.PutString("id", UserList[clickedPos].id);
        ......
;

【讨论】:

谢谢!我会试一试。是否有一种解决方案可以保留对我的 GetView 函数范围内的其他内容的访问权限? @MicronXD:好的,试试吧。你想在我的 GetView 范围内访问什么? @MicronXD:我的建议也是使用 ViewHolder Pattern 以获得良好的 ListView 性能,并避免再次重新创建已经创建的视图,请参阅下面的 Xamarin Android - deleting item from ListView deletes TWO items 可能更有帮助 这是正确的方法,但请记住,这会添加一个新的匿名事件处理程序,每次重用行时都无法取消注册。将委托逻辑转移到一个单独的方法中并取消注册,然后在每次调用 GetView 时重新注册。【参考方案2】:

ViewHolder 模式提及/建议的第一个 +1。你在正确的轨道上@MicronXD 但是我想鼓励你使用 ViewHolder 模式,它可以很好地利用你的行的视图重用。

接下来,您需要在您的 Activity 中创建一个方法,该方法实际上完成了启动另一个 Activity 的工作,并且这个新方法应该接受一个 int,它是具有您感兴趣的图像的对象的 ID,或者您可以传入整个对象。因此,例如,如果您从 MainActivity 实例化您的自定义适配器,那么您可以创建一个名为 public void OnThumbnailClicked(int id)

然后每次点击图片时,获取被点击对象的id,然后调用方法如下:(我用了一个客户的例子)

public override View GetView(int position, View convertView, ViewGroup parent)
        
            CustomerHolder holder = null;
            var view = convertView;

            if (view == null)
            
                view = Context.LayoutInflater.Inflate(Resource.Layout.CustomRow, null);

                holder = new CustomerHolder();
                holder.Name = view.FindViewById<TextView>(Resource.Id.textViewName);
                holder.Email = view.FindViewById<TextView>(Resource.Id.textViewEmail);
                holder.Phone = view.FindViewById<TextView>(Resource.Id.textViewPhone);
                holder.Image = view.FindViewById<ImageButton>(Resource.Id.imageViewThumbail);

                view.Tag = holder;
            
            else
            
                    holder = view.Tag as CustomerHolder;
            

            //At this point the holder holds reference to your view objects, whether they are 
            //recycled or created new. 
            //Next then you need to populate the views with the Customer info

            var Customer = Customers[position];
            holder.Name.Text = Customer.Name;
            holder.Email.Text = CustomerHolder.Email;
            holder.Phone.Text = Customer.Phone;
            holder.Image.SetImageResource = (Resource.Drawable.defaulthumbnail);
            holder.Image.Clickable = true;
            holder.Image.Click += (o, e) =>
            
                   var myActivity = (MainActivity)Context;
                   myActivity.OnThumbnailclicked((Customer[position).id);

            ;
            return view;
        

       private class CustomerHolder : Java.Lang.Object
        
            public TextView Name  get; set; 
            public TextView Email  get; set; 
            public TextView Phone  get; set; 
            public ImageView Thumbnail  get; set;                 
                


【讨论】:

以上是关于Monodroid - 处理重用 ListView 行的子视图上的事件的主要内容,如果未能解决你的问题,请参考以下文章

MonoDroid:未处理的异常恢复

我的 monodroid UI 元素(按钮、TextView...)中的枚举需要一个 .Net 包

ListView的重用

Android从源码看ListView的重用机制

Android性能优化之Listview(ViewHolder重用机制)

请你谈谈Listview和Recyclerview的区别