如何从RecyclerView适配器中返回数据?

6
我有一个 RecyclerView,在每个 RecyclerView 的对象中都有一个复选框。每次按下其中一个复选框时,我想在适配器上插入数据。完成一次后(按下片段上的按钮,将适配器设置为 RecyclerView),数据必须返回。
RecyclerView 的适配器代码(简化)如下:
public List<CarItem> data; 
public class MyCustomAdapter extends RecyclerView.Adapter<MyCustomAdapter.MyCustomViewHolder>  {

    public MyCustomAdapter(List<CarItem> data) {
        this.data=data;
    }

    public MyCustomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view= LayoutInflater.from(parent.getContext()).inflate(R.layout_car_item, parent, false);
        return new MyCustomViewHolder(view);
    }

    public void onBindViewHolder(final MyCustomViewHolder holder, final int position) {
        holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                  @Override
                  public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                       //Here I add the data that I want to store
                       holder.getItem();
                  }
            });
    }

    public class MyCustomViewHolder extends RecyclerView.ViewHolder{
          public MyCustomViewHolder (View itemView) {
              super(itemView);
              //Here I make reference to all the elements of the layout of the Adapter
          }

          public void getItem(){
              Log.d("prove", "" + data.size());
          }
    }
}

我已经创建了getItem()方法来获取MyCustomViewHolder中的数据,但我不知道如何将数据返回到设置AdapterRecyclerViewFragment中。
有没有一些方法可以从RecyclerViewAdapter将数据返回到Fragment中呢?
提前致谢!

@Ardock 是的,我正在从数据库中检索数据。 - Francisco Romero
@Ardock 因为这个项目的结构是由另一个人完成的,我正在尝试在每个列表项被选中时(勾选复选框)获取显示在列表上的数据。我必须尽可能少地修改代码。 - Francisco Romero
你是从数据库中检索carItems吗?如果你简化getItem()方法,我会改进我的回答。 - albodelu
@Ardock 是的,我正在从数据库中检索carItems。我认为我不能简化getItem()方法的内容,只能在控制台上记录日志。我创建它是为了尝试在设置适配器到回收视图的类中获取数据。 - Francisco Romero
好的,谢谢。我是在寻求更多信息而不是更少,我的英语…… 这一行 public List<CarItem> data; 不是在适配器里吗? - albodelu
显示剩余3条评论
3个回答

7

更新的回复:

使用CarItems列表的替代方案:

@OnClick(...)
public void onButtonClicked() {
    int size = ((MyCustomAdapter) mRecyclerView.getAdapter()).getItemCount();
    for (int i = 0; i < size; i++) {
        if (((MyCustomAdapter) mRecyclerView.getAdapter()).isItemChecked(i)) {
            // Get each selected item
            CarItem carItem = (MyCustomAdapter) mRecyclerView.getAdapter()).getItem(i);
            // Do something with the item like save it to a selected items array.
        }
    }
}

您可以在适配器中添加一个getCheckedItems()方法:
public List<CarItem> getCheckedItems() {
    List<CarItem> checkedItems = new ArrayList<>();
    for (int i = 0; i < getItemCount(); i++) {
        if (isItemChecked(i)) {
            checkedItems.add(getItem(i));
        }
    }
    return checkedItems;
}

然后像这样从RecyclerView中使用它:

((MyCustomAdapter) mRecyclerView.getAdapter()).getCheckedItems();

有没有一种方式可以从RecyclerView的适配器中返回数据到我设置它的Fragment中?

通常来说,要从您的Fragment中操作数据:

将以下方法添加到适配器中,并通过将其转换为您的自定义适配器,从Fragment中调用它们:((MyCustomAdapter) mRecyclerView.getAdapter()).getItem(...);

public void setListItems(List<CarItem> data) {
    this.data = data;
}

public List getListItems() {
    return data;
}

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

public CarItem getItem(int position) {
    return data.get(position);
}

public void setItem(CarItem item, int position) {
    data.set(position, item);
}

我有一个RecyclerView,在RecyclerView的每个对象中都有一个复选框。每次按下其中一个复选框,我想在适配器中插入数据。

管理多选状态和保存状态

请阅读此解释,并查看此多选示例应用程序

该库还使用SparseBooleanArray扩展了选择适配器以保存所选项目。

或者使用以下方法保存选中状态,并在单击按钮时仅访问所选项目的数据:

public class MyCustomAdapter extends RecyclerView.Adapter<MyCustomAdapter.MyCustomViewHolder>  {
    private ArrayList<CarItem> data;
    private SparseBooleanArray checkedState = new SparseBooleanArray();

    public void setCheckedState(int position, boolean checked) {
        checkedState.append(position, checked);
    }

    public boolean isItemChecked(int position) {
        return checkedState.get(position);
    }

    public SparseBooleanArray getCheckedState() {
        return checkedState;
    }

    public void onBindViewHolder(final MyCustomViewHolder holder, final int position) {
        holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            holder.checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                  @Override
                  public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                       setCheckedState(holder.getAdapterPosition(), isChecked);
                       //Here I add the data that I want to store
                       if (isChecked) {
                           data.set(holder.getAdapterPosition(), holder.getItem());
                       } else {
                           data.set(holder.getAdapterPosition(), null);
                       }

              }
        });
}

这个与Google I/O 2016相关的视频中使用适配器位置的原因

enter image description here


我创建了getItem()方法以获取MyCustomViewHolder内的数据,但我不知道如何将数据返回到将Adapter设置为RecyclerView的Fragment中。

向视图持有者设置/获取数据

这些方法也可以通过使用setTag()/getTag()机制来获取数据。

viewHolder.itemView.setTag(yourNewData);

/**
 * Returns this view's tag.
 *
 * @return the Object stored in this view as a tag, or {@code null} if not
 *         set
 *
 * @see #setTag(Object)
 * @see #getTag(int)
 */
@ViewDebug.ExportedProperty
public Object getTag() {
    return mTag;
}

/**
 * Sets the tag associated with this view. A tag can be used to mark
 * a view in its hierarchy and does not have to be unique within the
 * hierarchy. Tags can also be used to store data within a view without
 * resorting to another data structure.
 *
 * @param tag an Object to tag the view with
 *
 * @see #getTag()
 * @see #setTag(int, Object)
 */
public void setTag(final Object tag) {
    mTag = tag;
}

有一次,我完成了(按下片段上的一个按钮,将适配器设置为RecyclerView),数据必须返回。关于在数据返回时你要做什么以及在此处再次将适配器设置为RecyclerView的原因,我需要额外的信息。
可以使用findViewHolderForAdapterPosition(int)方法。
/**
 * Return the ViewHolder for the item in the given position of the data set. Unlike
 * {@link #findViewHolderForLayoutPosition(int)} this method takes into account any pending
 * adapter changes that may not be reflected to the layout yet. On the other hand, if
 * {@link Adapter#notifyDataSetChanged()} has been called but the new layout has not been
 * calculated yet, this method will return <code>null</code> since the new positions of views
 * are unknown until the layout is calculated.
 * <p>
 * This method checks only the children of RecyclerView. If the item at the given
 * <code>position</code> is not laid out, it <em>will not</em> create a new one.
 *
 * @param position The position of the item in the data set of the adapter
 * @return The ViewHolder at <code>position</code> or null if there is no such item
 */
public ViewHolder findViewHolderForAdapterPosition(int position) {
    if (mDataSetHasChangedAfterLayout) {
        return null;
    }
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
        final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
        if (holder != null && !holder.isRemoved() && getAdapterPositionFor(holder) == position) {
            return holder;
        }
    }
    return null;
}

@OnClick(...)
public void onButtonClicked() {
    int size = ((MyCustomAdapter) mRecyclerView.getAdapter()).getItemCount();
    for (int i = 0; i < size; i++) {
        ViewHolder vh = (MyCustomViewHolder) findViewHolderForAdapterPosition(i);
        if (vh != null && ((MyCustomAdapter) mRecyclerView.getAdapter()).isItemChecked(i)) {
            Object obj = vh.itemView.getTag();
            // Manipulate the data contained in this view holder
            // but perhaps would be better to save the data in the CarItem
            // and don't manipulate the VH from outside the adapter
        }
    }
}

1
非常感谢您详细的回答!我之前有些困惑,因为我认为onBindViewHolder方法是独立的,但最终我将getItem()方法放在了MyCustomAdapter中而不是onBindViewHolder方法之内。当然,我还得创建一个setter方法,在onBindViewHolder方法中使用它来在从我的“Fragment”检索元素之前将其放置。您的第二部分帮助我澄清了思路。 - Francisco Romero

0

最简单的方法是使用EventBus https://github.com/greenrobot/EventBus

只需在您的片段中注册它并设置@Subscribe方法。 构建简单的类并使用EventBus.getDefault().post(new YourClass(yourData))将数据从适配器传递到片段。

另一种方法是使用接口。

注意!

当您在适配器中使用复选框时,必须记住复选框的旧值,并在滚动列表时进行设置。然后您可能会对SparseBooleanArray感兴趣。


2
我没有使用过EventBus,但我使用过Otto,它可以异步处理事务。但是,这可能会变得非常混乱。因此,在使用总线时要小心。我更喜欢使用接口。 - Todor Kostov

-2
不要使用 RecyclerView,它并不是为这种目的而设计的。当你想要显示只读数据时,RecyclerViewListView 是有用的,因为它们的行会被回收利用。但是,使用这些视图从用户获取输入是危险的,因为在滚动后回收行时,可能会丢失已经输入的数据。相反,应该使用 LinearLayout。这样你就可以直接访问其中的视图。如果你要显示的行数不是固定的,可以动态地向容器中添加 View

我正在使用RecyclerView,因为我不知道从数据库中检索到多少项。我不确定是否可以使用LinearLayout实现这一点。 - Francisco Romero
@Kiril,你有相关的资料吗?我可以想象RecyclerView/Listview在某些情况下非常有用。 - Robin Dijkhof
可以实现。您只需要将视图(行)展开并将其作为子视图放入“LinearLayout”中即可。 - Kiril Aleksandrov
Error404 我认为 RecyclerView 是多用途的,而这个答案是不正确的。我从未将其用于您所需的检查要求,只将其用于只读情况(因此我不会对此进行投票),但如果您在不知道项目数量的情况下遵循此方法,则会失去回收利益,我认为所有可能的问题都可以得到解决。否则 Recyclerview 将不存在。 - albodelu
@adrock,我已经尝试过这个方法,确实有效。为了保持滚动功能,应该将ListView放在ScrollView中(其他ViewGroup容器也适用)。 - Kiril Aleksandrov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接