【问题标题】:RecyclerView item selector crashing when current selected item is off screen当前选定的项目不在屏幕上时,RecyclerView 项目选择器崩溃
【发布时间】:2015-09-16 18:22:12
【问题描述】:

对于一项作业,我必须使用一些预定义的项目创建一个 recyclerview,然后单击它们中的每一个在另一个视图中执行一些操作。这一切都很好,对于我的任务来说可能已经足够了,但我对它不太满意,因为没有显示选择了什么项目等等。

所以我想在列表项被选中时突出显示背景。经过一番搜索,我找到了一个类似问题的答案:https://stackoverflow.com/a/28617619/2437682

我遵循了答案,并得到了令我非常满意的结果,直到我注意到如果在单击新项目时当前选定的项目不在屏幕上,则会导致应用程序因 NPE 而崩溃。

导致此崩溃的特定代码是:

void onItemClick(int position) {

        ListItemViewHolder yourViewHolder;

        int oldSelectedPosition = mSelectedIndex;

        if (position != mSelectedIndex) {
            mSelectedIndex = position;


            yourViewHolder = (ListItemViewHolder) recyclerView.findViewHolderForAdapterPosition(oldSelectedPosition);

            //this is the line that causes the crash when the view is off-screen
            yourViewHolder.itemView.setSelected(false);


            yourViewHolder = (ListItemViewHolder) recyclerView.findViewHolderForAdapterPosition(mSelectedIndex);
            yourViewHolder.itemView.setSelected(true);
        }
    }

我明白发生了什么。如果项目不在屏幕上,findViewHolderForAdapterPosition 方法将返回 null,因此我无法更改 setSelected 属性。

我不确定如何解决它。我试着说:

if (yourViewHolder != null) {
    yourViewHolder.itemView.setSelected(false);
}

这至少可以防止应用程序崩溃,但是下次该列表项滚动回屏幕时,它仍然像被选中一样突出显示。

有什么想法吗?

更新

这是更新后的适配器和查看器类:

public class ColorsAdaptor extends RecyclerView.Adapter<ListItemViewHolder>{

    @Override
    public ListItemViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
        Context context = viewGroup.getContext();
        LayoutInflater inflater = LayoutInflater.from(context);

        View listItemView = inflater.inflate(R.layout.list_item, viewGroup, false);

        ColorDrawable colorDrawableSelected =
                new ColorDrawable(getResources().getColor(R.color.selected));

        StateListDrawable stateListDrawable = new StateListDrawable();
        stateListDrawable.addState(new int[]{android.R.attr.state_selected}, colorDrawableSelected);
        stateListDrawable.addState(StateSet.WILD_CARD, getResources().getDrawable(R.drawable.border));

        listItemView.setBackgroundDrawable(stateListDrawable);

        ListItemViewHolder viewHolder = new ListItemViewHolder(listItemView);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(ListItemViewHolder listItemViewHolder, int position) {
        listItemViewHolder.updateView();

        MyColor color = items.get(position);

        TextView colorText = listItemViewHolder.getColorText();
        colorText.setText(color.getName());

        ImageView colorBlock = listItemViewHolder.getColorBlock();
        colorBlock.setBackgroundColor(color.getColorID());
    }

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

public class ListItemViewHolder extends RecyclerView.ViewHolder{
    private TextView colorText;
    private ImageView colorBlock;
    private View itemView;

    public ListItemViewHolder(View view) {
        super(view);
        itemView = view;
        this.colorBlock = (ImageView) view.findViewById(R.id.colorBlock);
        this.colorText = (TextView) view.findViewById(R.id.colorText);

        itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int position = getAdapterPosition();

                if (position != mSelectedIndex) {

                   ListItemViewHolder oldViewHolder = (ListItemViewHolder) recyclerView.findViewHolderForAdapterPosition(mSelectedIndex);
                   if (oldViewHolder != null) {
                       oldViewHolder.itemView.setSelected(false);
                   }

                    itemView.setSelected(true);
                    mSelectedIndex = position;
                }



                ImageView temp = (ImageView) v.findViewById(R.id.colorBlock);
                colorViewer.setBackground(temp.getBackground());
            }
        });
    }

    public void updateView(){
        Log.d("", "updateView SelectedIndex: " + mSelectedIndex + " adapterPosition: " + getAdapterPosition());
        this.itemView.setSelected(getAdapterPosition() == mSelectedIndex);
    }

    public TextView getColorText() {
        return colorText;
    }

    public ImageView getColorBlock() {
        return colorBlock;
    }

}

哦,还有 updateView() 方法的调试输出。

第一次运行时:

SelectedIndex: 0 adapterPosition: 0
SelectedIndex: 0 adapterPosition: 1
SelectedIndex: 0 adapterPosition: 2
SelectedIndex: 0 adapterPosition: 3
SelectedIndex: 0 adapterPosition: 4
SelectedIndex: 0 adapterPosition: 5
SelectedIndex: 0 adapterPosition: 6

然后,当我向上和向下滚动几次,当新视图返回屏幕时调用 updateView:

SelectedIndex: 0 adapterPosition: 7
SelectedIndex: 0 adapterPosition: 9
SelectedIndex: 0 adapterPosition: 2
SelectedIndex: 0 adapterPosition: 0
SelectedIndex: 0 adapterPosition: 7
SelectedIndex: 0 adapterPosition: 9
SelectedIndex: 0 adapterPosition: 2
SelectedIndex: 0 adapterPosition: 0
SelectedIndex: 0 adapterPosition: 7
SelectedIndex: 0 adapterPosition: 9

看看它在滚动时是如何跳过adapterPositions的?因此,如果在滚动期间adapterPosition 8 永远不会恢复,那么adapterPosition8 处的视图永远不会在updateView() 方法中关闭。

【问题讨论】:

  • 你适配了adapter的onBindViewHolder()来设置itemViews的选择状态了吗?

标签: android android-recyclerview


【解决方案1】:

您必须在“onBindViewHolder”方法中保持项目状态。 此外,您应该管理视图持有者内的 onItemClick,因为在那里,您正是您想去的地方,所以基本上您将拥有:

YourAdapter.java

@Override
public void onBindViewHolder(YourViewHolder yourViewHolder, int i) {
    yourViewHolder.updateView();
}

YourViewHolder.java

class YourViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener{ 
    View itemView;
    public YourViewHolder(View itemView){
        this.itemView = itemView;
    }

    public void updateView(){
        //Do whatever you need to do regarding the item view
        this.itemView.setSelected(getAdapterPosition() == mSelectedIndex);
    }

    @Override
    public void onClick(View v) {
        int position = getAdapterPosition();
        if (position == mSelectedIndex){
            //It's the same view, deselect it or just leave it as it is
        } else {
           this.itemView.setSelected(true);
           mSelectedIndex = position;
        }
    }
}

请记住,您不应将 mSelectedIndex 放在 ViewHolder 中,而应放在单独的类中。

【讨论】:

  • 好吧,我照你说的做了,而且有点用,但还有一个奇怪的问题。因此,在 updateView() 方法中,我放置了一个调试日志,打印出 getAdapterPosition() 和 mSelectedIndex。因此,当我滚动并回收新视图时,adapterPosition 会发生变化。除了每次滚动似乎改变了 2 个位置而不是 1。IE,如果 getAdapterPosition() 打印出 2,那么下一次向上滚动,它打印出 0。这意味着我的奇数适配器位置永远不会通过 updateView( ) 并被取消选中。有什么想法吗?
  • 你能用你的新实现更新你的帖子以便我检查吗?
  • 您应该像我在查看器中那样实现 View.OnClickListener 并在那里管理所有点击内容。还可以使用 updateView 方法更新与视图和构造函数有关的任何其他内容,以绑定视图。
  • 我不确定实现 OnClickListener 与拥有匿名内部类有何不同,但好吧,我按照你的方式做了。不过,我仍然对你的意思感到困惑。获取我单击的视图很好,它获取了已选择但已从屏幕滚动出来的 OTHER 视图,这给我带来了麻烦。
  • 如果你使用getLayoutPosition(),它的输出是什么?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-26
相关资源
最近更新 更多