在RecyclerView中突出显示所选项目

14

我正在尝试在RecyclerView中突出显示一个项目,类似于在ListView中设置选定的项目。

目前,我已经设置了RecyclerView,有一个默认的LayoutManager,并且有一个显示所有数据的适配器。最近我刚刚让onClickListener()起作用(虽然我不确定它应该放在onCreateViewHolder()还是onBindViewHolder() - 不确定onBindViewHolder()何时被调用)。

我已经尝试搜索过,最接近的是这里的问题。然而,我完全迷失在那个问题中。那个onClick()方法在哪里(在适配器内部、在包含活动/片段内、等等)?那个viewHolderListener是什么?getPosition()是从哪里来的,它确切地做了什么?基本上,我从那个问题中一无所获,这是我到目前为止能找到的最好的资源。

这是我当前设置RecyclerView的代码:

// Sets up the main navigation
private void setupMainNav() {
    // use this setting to improve performance if you know that changes
    // in content do not change the layout size of the RecyclerView
    mMainRecyclerNav.setHasFixedSize(true);

    // use a linear layout manager
    LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
    mMainRecyclerNav.setLayoutManager(layoutManager);

    // Create and set the adapter for this recyclerView
    MainNavAdapter adapter = new MainNavAdapter(getActivity());
    mMainRecyclerNav.setAdapter(adapter);
}

这是我目前的适配器代码:

class MainNavAdapter extends RecyclerView.Adapter<MainNavAdapter.ViewHolder> {
    Context mContext;

    // Holds the titles of every row
    String[] rowTitles;

    // Default constructor
    MainNavAdapter(Context context) {
        this.mContext = context;

        // Get the rowTitles - the necessary data for now - from resources
        rowTitles = context.getResources().getStringArray(R.array.nav_items);
    }

    // Simple class that holds all the views that need to be reused
    class ViewHolder extends RecyclerView.ViewHolder{
        View parentView; // The view which holds all the other views
        TextView rowTitle; // The title of this item

        // Default constructor, itemView holds all the views that need to be saved
        public ViewHolder(View itemView) {
            super(itemView);

            // Save the entire itemView, for setting listeners and usch later
            this.parentView = itemView;

            // Save the TextView- all that's supported at the moment
            this.rowTitle = (TextView) itemView.findViewById(R.id.row_title);
        }
    }

    // Create new views (invoked by the layout manager)
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        // Create the view for this row
        View row = LayoutInflater.from(mContext)
                .inflate(R.layout.list_navmain_row, viewGroup, false);

        // Create a new viewHolder which caches all the views that needs to be saved
        ViewHolder viewHolder = new ViewHolder(row);

        return viewHolder;
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder viewHolder, int i) {
        // - get element from your dataset at this position
        // - replace the contents of the view with that element
        viewHolder.rowTitle.setText(rowTitles[i]);

        // TODO: Make a better workaround for passing in the position to the listener
        final int position = i;



        // Set a listener for this entire view
        viewHolder.parentView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getActivity(), "Clicked on row: " + position, Toast.LENGTH_SHORT).show();
            }
        });
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return rowTitles.length;
    }
}

我希望能得到任何帮助。谢谢。


也许这可以帮助在RecycleView中突出显示列表项 http://amolsawant88.blogspot.in/2015/08/easy-way-to-highlight-selected-rowitem.html - Amol Sawant
4个回答

18

RecyclerView 不像 ListView 那样处理项目选择或状态。相反,您需要在视图持有者中手动处理这些。

您可以做的第一件事是将项目视图声明为可点击,在您的 `ViewHolder` 构造函数中:

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

        // Make this view clickable
        itemView.setClickable(true);

        // ...
    } 

如果您想突出显示选择内容,则必须在适配器中跟踪所选行(例如使用索引列表),并在您的onBindViewHolder方法中:

@Override 
public void onBindViewHolder(ViewHolder viewHolder, int i) {
    // mark  the view as selected:
    viewHolder.parentview.setSelected(mSelectedRows.contains(i));
} 

顺便提一下,在onCreateViewHolder中设置父视图的onClickListener,而不是在onBindViewHolder中设置。onBindViewHolder方法将为同一视图持有者调用多次,并且您将执行不必要的更多操作。


@MDragon00 如果您还需要通过键盘移动选择,请查看我在此处提供的解决方案:https://dev59.com/X14d5IYBdhLWcg3wFPM7 - Greg Ennis
我认为onBindViewHolder应该在需要显示行中的项目时使用。 我猜这个问题只是涉及到仅在选择了某个项目时突出显示它,而不是在创建期间突出显示它。 那么你为什么建议在onBindViewHolder中使用setSelected方法呢?既然你的答案已经被接受,你可以回答我的问题 :) https://dev59.com/14nda4cB1Zd3GeqPEMwb - Cris
因为onBind方法接收一个可能被选择或未选择(且不再使用)的回收视图,并从其中提取适配器中的数据。这就是为什么你需要知道该元素是否已经被选择。请查看我的https://github.com/davideas/FlexibleAdapter。 - Davideas
@ScottBiggs 啊,我了解你的意思。是的,我现在也遇到了同样的问题。通常通过保留对View的引用来解决此问题,但由于它们被回收利用,如果您滚动太远以致于屏幕外,这种方法就不起作用了。我还没有找到解决方案 :( - Shawn Lauzon
嗨@ScottBiggs,请看下面的回答,解决了这个问题 :) - Shawn Lauzon
显示剩余3条评论

10
有一个简单的方法来做到这一点:
  int row_index=0;
    @Override
    public void onBindViewHolder(MyViewHolder holder, final int position) {
        Movie movie = moviesList.get(position);
        holder.title.setText(movie.getTitle());
        holder.genre.setText(movie.getGenre());
        holder.year.setText(movie.getYear());

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                row_index=position;
                notifyDataSetChanged();
            }
        });

        if(row_index==position){
            holder.itemView.setBackgroundColor(Color.parseColor("#567845"));
        }
        else
        {
            holder.itemView.setBackgroundColor(Color.parseColor("#ffffff"));
        }

    }

4
我认为您应该使用notifyItemChanged(mSelectedPosition)和notifyItemChanged(getOldPosition()),而不是使用notifyDataSetChanged。请注意,在这种情况下,您仍需要在适当的位置更新mSelectedPosition和getOldPosition()的值。 - Android is everything for me

5

我扩展了@XGouchet的答案,因为即使它让我走上了正确的道路,我仍然需要做额外的工作来支持选择和取消选择行(如切换)。@ScottBiggs和我都需要这种支持。

代码保存了mSelectedPosition,允许在onBindViewHolder中正确设置选择,以及mSelectedView,允许在单击时正确切换选择(开和关)。

private class AsanaAdapter extends RecyclerView.Adapter<AsanaViewHolder> {
    private RecyclerView mRecyclerView;

    private final List<Asana> mAsanas;
    private int mSelectedPosition;
    private View mSelectedView;

    public AsanaAdapter(RecyclerView recyclerView, List<Asana> items) {
        mRecyclerView = recyclerView;
        mAsanas = items;
        setHasStableIds(true);
        setSelected(0);
    }

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

    @Override
    public AsanaViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View newView = getLayoutInflater().inflate(
                R.layout.performance_builder_list_content, parent, false);
        newView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (!v.isSelected()) {
                    // We are selecting the view clicked
                    if (mSelectedView != null) {
                        // deselect the previously selected view
                        mSelectedView.setSelected(false);
                    }
                    mSelectedPosition = mRecyclerView.getChildAdapterPosition(v);
                    mSelectedView = v;
                    setTitle(mAsanas.get(mSelectedPosition).getName());
                } else {
                    // We are deselecting the view clicked
                    setTitle("");
                    mSelectedPosition = -1;
                    mSelectedView = null;
                }

                // toggle the item clicked
                v.setSelected(!v.isSelected());
            }
        });
        return new AsanaViewHolder(newView);
    }

    @Override
    public void onBindViewHolder(AsanaViewHolder holder, int position) {
        if (position == mSelectedPosition) {
            holder.itemView.setSelected(true);

            // keep track of the currently selected view when recycled
            mSelectedView = holder.itemView;
        } else {
            holder.itemView.setSelected(false);
        }
    }
}

测试你的代码花了一些时间(我不得不重新编写点击监听器并找出一些奇怪的方法调用),但似乎工作正常。非常棒,谢谢! - SMBiggs

2
如果您想在点击后仅突出显示所选项目一段时间,请尝试此解决方案。我使用retrolambda,但您也可以使用普通的Runnable对象。
1.
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{
    @BindView(R.id.appIcon)
    ImageView appIcon;
    @BindView(R.id.appName)
    TextView appName;

    public ViewHolder(View itemView) {
        super(itemView);
        itemView.setOnClickListener(this);
        ButterKnife.bind(this, itemView);
    }

    @Override
    public void onClick(View v) {
        v.setSelected(true);
        new Handler().postDelayed(() -> v.setSelected(false), 100);
        //or new Handler().postDelayed(new Runnable...

        //your onClick action
    }
}
  1. create list_item_selector.xml in your Drawable directory

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
        <item android:state_selected="true" android:drawable="@color/colorPrimary" />
        <item android:drawable="@android:color/transparent" />
    </selector>
    
  2. Set your selector in root layout of your single item

    android:background="@drawable/list_item_selector"
    

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