Android RecyclerView 切换布局(grid/list)使用旧的 viewHolders

Android RecyclerView switching layouts (grid/list) old viewHolders used

问题总结

我在 activity 中有一个 RecyclerView 列表,带有一个用于在 LIST 和 GRID 布局之间切换 RecyclerView 的 actionBar 按钮。

以 LIST 和 GRID 形式查看列表时,项目的布局不同。

查看 LIST 中的项目时,行格式为

|----|
|icon|  Item Name
|----|

在 GRID 中查看项目时,行格式为

|--------------|
| large icon   |
|              |
|              |
|              |      
|--------------|
    Item Name

因此按照我看到的列表查看我的列表

|----|
|icon|  Item1
|----|
|----|
|icon|  Item2
|----|
|----|
|icon|  Item3
|----|
|----|
|icon|  Item3
|----|

当我以网格形式查看我的列表时,我看到

|--------------|    |--------------|
| large icon   |    | large icon   |      
|              |    |              |      
|              |    |              |      
|              |    |              |      
|--------------|    |--------------|
    Item1                Item2
|--------------|    |--------------|
| large icon   |    | large icon   |
|              |    |              |
|              |    |              |
|              |    |              |      
|--------------|    |--------------|
    Item3                Item4

当我使用 actionBar 按钮将列表视图从 LIST 切换到 GRID 或从 GRID 切换到 LIST 时,某些视图仍呈现在其他样式的布局中。我相信您在切换之前滚动到视线之外的视图会被回收和重复使用,而不是重新创建。

例如,如果我从 GRID 转到 LIST,我会看到

                    |--------------|
                    | large icon   |      
|----|              |              |      
|icon|  Item1       |              |      
|----|              |              |      
                    |--------------|
                         Item2
|--------------|    |--------------|
| large icon   |    | large icon   |
|              |    |              |
|              |    |              |
|              |    |              |      
|--------------|    |--------------|
    Item3                Item4

后台代码信息

我有 2 个布局管理器;每种布局样式一个

private RecyclerView.LayoutManager linearLayoutManager;
private RecyclerView.LayoutManager gridLayoutManager;

在适配器中我存储我是将列表显示为网格还是列表

enum ListViewEnum{
    LIST,
    GRID
}

ListViewEnum listViewStyle = ListViewEnum.LIST;

public ListViewEnum getListStyle() {
    return listViewStyle;
}

列表中项目的布局取决于列表是呈现为列表还是网格。

ViewHolderList
ViewHolderGrid

创建视图持有者时,我将视图持有者的类型基于适配器列表视图样式

protected RecyclerView.ViewHolder getItemViewHolder(ViewGroup parent){
    if (listViewStyle == ListViewEnum.LIST){
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_row, parent, false);
        return new ViewHolderList(itemView, ....);
    } else {
        View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.grid_row, parent, false);
        return new ViewHolderGrid(itemView, ....);
    }
}

当用户单击 actionBar 按钮时,我切换适配器的值

getListAdapter().setListViewMode(ListViewMode.LIST);

我再刷新

listView.setLayoutManager(getListAdapter().getListStyle() == ListViewEnum.LIST ? linearLayoutManager : gridLayoutManager);
getListAdapter().notifyDataSetChanged();
listView.invalidateItemDecorations();
invalidateOptionsMenu();

我知道我正在刷新列表适配器,但它并没有放弃 recycledViews。调试显示,在单击切换按钮时,我看到某些位置(回收的位置)和 "onCreateViewHolder" 和 "onBindViewHolder"(对于新位置)仅调用 "onBindViewHolder",因此表明旧视图持有人正在回收。

如何刷新列表,使其不会重复使用错误列表样式的 ViewHolder?

由于 RecyclerViews 内存管理,RecyclerView 尝试重用视图以更快地滚动。我认为使用两个适配器实例而不是一个适配器实例并用选定的适配器重新填充回收器可能会有所帮助。

@Abbas 的评论是正确的,getItemViewType() 可能是您最好的选择。根据您的数据,您也许可以拥有一个持有人,但如果数据项不同,您可能必须创建两个。使用适配器内部的 getItemViewType() 调用来确定要显示的内容。您必须将标志从 activity 传递到适配器或回调到 activity 以获取类型。

public static final int SMALL_VIEW = 0;
public static final int LARGE_VIEW = 1;
private int mType = 0;

@Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v;
        RecyclerView.ViewHolder viewHolder = null;

        // Create the Holders depending on the type, the view type
        // will come from the getItemViewType() call.

        switch ( viewType ) {
            case SMALL_VIEW:
                v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_row, parent, false);
                viewHolder = new ViewHolderList(mContext, v);
                break;

            case LARGE_VIEW:
                v = LayoutInflater.from(parent.getContext()).inflate(R.layout.grid_row, parent, false);

              viewHolder = new ViewHolderGrid(mContext, v);
            default:

        }
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {

        // This switch may not be necessary if you are using 
        // the same holder for both items.
        switch ( getItemViewType(position) ) {
            case SMALL_ITEM:
                ViewHolderList list = (ViewHolderList)holder;
                list.setData(mData.get(position));
                break;
            case LARGE_ITEM:
                ViewHolderGrid grid = (ViewHolderGrid)holder;
                grid.setData(mData.get(position));
        }
    }

    @Override
    public int getItemViewType(int position) {
        // Normally you identify the data type at position and
        // switch the view, for your application you can switch
        // for all.
        return mType;
    }

    public void setViewType(int type) {
       mType = type;
    }