【问题标题】:Is this a wrong way to implement a RecyclerView in Android?这是在 Android 中实现 RecyclerView 的错误方法吗?
【发布时间】:2019-11-18 16:47:47
【问题描述】:

我正在尝试在我的应用中实现 RecyclerView。这是我的适配器类:

public class AdapterItemsList extends RecyclerView.Adapter<AdapterItemsList.ViewHolderItems>
{
    private ArrayList<CItem> items;

    public AdapterItemsList(ArrayList<CItem> items)
    {
        this.items = items;
    }

    @NonNull
    @Override
    public ViewHolderItems onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
    {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_counter, null, false);

        return new ViewHolderItems(view);
    }

    @Override
    public void onBindViewHolder(@NonNull final ViewHolderItems holder, final int position)
    {
        holder.asignarDatos(items.get(holder.getAdapterPosition()));

        holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {

                //deleteSelectedItem() method gonna be here

                Toast.makeText(view.getContext(), String.valueOf(holder.getAdapterPosition())
                        , Toast.LENGTH_SHORT).show();
                return false;
            }
        });

        holder.counterAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Integer auxInteger = Integer.parseInt(holder.itemNumber.getText().toString());
                auxInteger += 1;
                holder.itemNumber.setText(auxInteger.toString());
                items.get(holder.getAdapterPosition()).setObjectNumber(Integer.parseInt(holder.itemNumber.getText().toString()));
            }
        });

        holder.counterSubtract.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Integer auxInteger = Integer.parseInt(holder.itemNumber.getText().toString());
                if (auxInteger > 0)
                {
                    auxInteger -= 1;
                    holder.itemNumber.setText(auxInteger.toString());
                    items.get(holder.getAdapterPosition()).setObjectNumber(Integer.parseInt(holder.itemNumber.getText().toString()));
                }
            }
        });
    }

    @Override
    public int getItemCount()
    {
        return items.size();
    }

    public class ViewHolderItems extends RecyclerView.ViewHolder
    {
        TextView itemName;
        TextView itemNumber;
        Button counterAdd;
        Button counterSubtract;

        public ViewHolderItems(View itemView)
        {
            super(itemView);

            itemName = (TextView) itemView.findViewById(R.id.textViewItemCounterName);
            itemNumber = (TextView) itemView.findViewById(R.id.textViewItemCounterNumber);
            counterAdd = (Button) itemView.findViewById(R.id.buttonItemCounterAdd);
            counterSubtract = (Button) itemView.findViewById(R.id.buttonItemCounterSubtract);
        }

        public void asignarDatos(CItem item)
        {
            itemName.setText(item.getObjectName());
            itemNumber.setText(item.getObjectNumber().toString());
        }
    }
}

“创建”适配器的代码:

private void CreateAdapter() {
    recyclerViewItems.setAdapter(null);
    items.clear();

    //I dont post the code, but this just gets data from a SQLite database and populate "items".
    LoadClientsList();

    final AdapterItemsList adapter = new AdapterItemsList(items);

    recyclerViewItems.setAdapter(adapter);
}

如您所见,我从不使用“实现 View.OnLongClickListener”或类似的东西,但我只是在 onBindViewHolder 中设置了 OnClickListener。现在里面有一个“Toast”(当我测试应用程序时它看起来很好),但稍后应该有我的“删除项目”对话框,它会给用户更改接受或取消(一个常见的对话框) .如果用户按“接受”,那么我将从列表中删除该项目。

我承认,我不是 Android 或 Java Ninja,所以我知道可能存在可怕的错误。但问题是,该应用程序现在正在运行(当我长按一个项目时,Toast 会正确显示),但事实上我从不使用“实现”,而我看到每个人都在与 RecyclerViews 和适配器相关的 StackOverflow 问题中这样做,让我觉得我犯了(几个)严重错误。

我可以承认,因为我不是 Java 基础知识的大师(已经使用 COBOL 工作了几年,我记得 OOP 几乎是 NULL),我不知道我的错误在哪里,但仍然非常感谢任何帮助。

谢谢!


更新: 好的,我正在更改我的代码,现在如下所示:

public class AdapterItemsList extends
RecyclerView.Adapter<AdapterItemsList.ViewHolderItems> implements
View.OnClickListener {
private ArrayList<CItem> items;

/* Added this */
private View.OnClickListener listener;

public AdapterItemsList(ArrayList<CItem> items)
{
    this.items = items;
}

@NonNull
@Override
public ViewHolderItems onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
{
    View view = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.item_counter, null, false);

  /* Added this */
    view.setOnClickListener(this);

    return new ViewHolderItems(view);
}

@Override
public void onBindViewHolder(@NonNull final ViewHolderItems holder, final int position)
{
    holder.asignarDatos(items.get(holder.getAdapterPosition()));

    holder.counterAdd.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Integer auxInteger = Integer.parseInt(holder.itemNumber.getText().toString());
            auxInteger += 1;
            holder.itemNumber.setText(auxInteger.toString());
            items.get(holder.getAdapterPosition()).setObjectNumber(Integer.parseInt(holder.itemNumber.getText().toString()));
        }
    });

    holder.counterSubtract.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Integer auxInteger = Integer.parseInt(holder.itemNumber.getText().toString());
            if (auxInteger > 0)
            {
                auxInteger -= 1;
                holder.itemNumber.setText(auxInteger.toString());
                items.get(holder.getAdapterPosition()).setObjectNumber(Integer.parseInt(holder.itemNumber.getText().toString()));
            }
        }
    });
}

@Override
public int getItemCount() { [...] }

/* Added this */
public void setOnClickListener(View.OnClickListener listener)
{
    this.listener = listener;
}

/* Added this */
@Override
public void onClick(View view) {
    if (listener != null)
    {
        listener.onClick(view);
    }
}

public class ViewHolderItems extends RecyclerView.ViewHolder { [...] } }

我创建适配器的代码:

private void CreateAdapter() {
    recyclerViewItems.setAdapter(null);
    items.clear();
    LoadClientsList();

    final AdapterItemsList adapter = new AdapterItemsList(items);

    /* Added this */
    adapter.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Toast.makeText(getApplicationContext(), String.valueOf(recyclerViewItems.getChildAdapterPosition(view)), Toast.LENGTH_SHORT).show();
        }
    });

    recyclerViewItems.setAdapter(adapter);
}

“recyclerViewItems.getChildAdapterPosition(view)”终于解决了我的问题(之前没有提到,但这就是我将“setOnClick...”放在onBindViewHolder方法中的原因,只是因为我需要的位置按下的项目,我不知道如何访问它)。 所以现在我在创建适配器时设置监听器,而不是在绑定视图持有者时设置监听器,我想这样会更好。 不过,我没有更改“holder.counterAdd.setOnClickListener”和“holder.counterSubtract.setOnClickListener”行,因为我认为它们必须在onBindViewHolder方法中,但我想我还是错了。

【问题讨论】:

  • 您的解决方案比implements View.OnClickListener 更好,因为您为每个项目分配了一个实现View.OnClickListner 接口的匿名类实例。
  • 更糟糕的是,监听器被设置在onBindViewHolder 中,这将被调用多次。每次用户滚动时,onBindViewHolder 会为每个可见项目调用,然后是一些。所以,这个例子不仅是为固定数量的可见项目创建匿名对象,而且每次调用onBindViewHolder
  • @sonnet 听起来很糟糕,因为你这样提到它。我会进行更多调查,看看如何使用“工具”。
  • 将“setOnClickListener”放入“onCreateViewHolder”方法会改变什么吗?至少它不会被多次调用,还是我错了?
  • 现在它可能正在泄漏调用此CreateAdapter() 的任何位置的上下文。因为匿名类持有对其父类的引用(参见stackoverflow.com/questions/5054360/…)编辑:这就是@MikeM.- 前一个没有泄漏上下文。

标签: android android-recyclerview android-adapter android-viewholder


【解决方案1】:

我不同意大多数 cmets 关于您的问题,我认为您编写适配器的方式非常好。这不是完美,但手动将View.OnClickListener 设置为ViewHolder 中的一些(或许多)视图并没有错。事实上,这是 Google 推荐的做事方式!

不过,我会做出一些改变。第一个是将单击侦听器的声明移动到onCreateViewHolder() 方法中(通过将它们放在 ViewHolder 的构造函数中)。第二种方法是添加对NO_POSITION 的检查,以确保您可以处理已从适配器中删除项目但尚未重新计算布局的情况。

以下是我的建议:

public ViewHolderItems(View itemView)
{
    super(itemView);

    itemName = (TextView) itemView.findViewById(R.id.textViewItemCounterName);
    itemNumber = (TextView) itemView.findViewById(R.id.textViewItemCounterNumber);
    counterAdd = (Button) itemView.findViewById(R.id.buttonItemCounterAdd);
    counterSubtract = (Button) itemView.findViewById(R.id.buttonItemCounterSubtract);

    itemView.setOnLongClickListener(new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View view) {
            deleteItem();
            return true;
        }
    });

    counterAdd.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            itemAdd();
        }
    });

    counterSubtract.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            itemSubtract();
        }
    });
}

private void deleteItem() {
    int position = getAdapterPosition();

    if (position != RecyclerView.NO_POSITION) {
        items.remove(position);
        adapter.notifyItemRemoved(position);
    }
}

private void itemAdd() {
    int position = getAdapterPosition();

    if (position != RecyclerView.NO_POSITION) {
        CItem item = items.get(position);
        item.setObjectNumber(item.getObjectNumber() + 1);
        adapter.notifyItemChanged(position);
    }
}

private void itemSubtract() {
    int position = getAdapterPosition();

    if (position != RecyclerView.NO_POSITION) {
        CItem item = items.get(position);

        if (item.getObjectNumber() > 0) {
            item.setObjectNumber(item.getObjectNumber() - 1);
            adapter.notifyItemChanged(position);
        }
    }
}

然后你的onBindViewHolder() 就非常简单了:

@Override
public void onBindViewHolder(@NonNull ViewHolderItems holder, int position)
{
    holder.asignarDatos(items.get(position));
}

那么,让我们谈谈我在这里做什么。

首先,我将所有侦听器设置推送到 ViewHolder 的构造函数中。这意味着侦听器都只分配一次。为了在 ViewHolders 被回收并附加到不同项目的世界中仍然发挥作用,我们必须使用 getAdapterPosition() 来确保我们始终与您列表中的正确项目进行交互。

我们必须检查NO_POSITION,因为 RecyclerView 布局操作是异步的:当您从列表中删除一个项目时,有一个间隙,该项目不再存在于您的适配器中,但仍显示在屏幕上。在这个间隙中,用户可能会点击一个按钮!在这些情况下,我们只是忽略用户输入。

我还更改了更新项目值的方式以及显示这些更新的方式。我总是通过适配器而不是获取文本并解析它然后重新设置它。请记住,您的 RecyclerView 实现的目标是能够正确显示列表中的任何元素。最好的方法是更新数据集中的元素,然后通知 RecyclerView 该元素已更改,并让onBindViewHolder() 处理所需的更新。

【讨论】:

  • 请注意,您应该final 关键字添加到onBindViewHolder() 中的position 参数。增量更新(例如notifyItemInserted()不会重新绑定仅移动的其他视图,因此您将拥有陈旧的position 值,这可能会导致错误。
猜你喜欢
  • 2020-06-27
  • 2013-12-02
  • 2012-11-11
  • 2019-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-10
相关资源
最近更新 更多