为什么 RecyclerView 没有 onItemClickListener() ?

问题

我正在探索RecyclerView,我惊讶地发现 RecyclerView 没有 onItemClickListener()。因为RecyclerView 继承自 android.view.ViewGroupListView 继承自 android.widget.AbsListView。但是我通过在RecyclerView.Adapter中编写onClick来解决我的问题:

public static class ViewHolder extends RecyclerView.ViewHolder implements OnClickListener {

    public TextView txtViewTitle;
    public ImageView imgViewIcon;

    public ViewHolder(View itemLayoutView) {
        super(itemLayoutView);
        txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
        imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
    }

    @Override
    public void onClick(View v) {

    }
}

但我仍然想知道为什么 Google 删除 onItemClickListener()?是否存在性能问题或其他问题?


#1 热门回答(1128 赞)

tl; dr 2016使用 RxJava 和 PublishSubject 公开点击的 Observable。

public class ReactiveAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    String[] mDataset = { "Data", "In", "Adapter" };

    private final PublishSubject<String> onClickSubject = PublishSubject.create();

    @Override 
    public void onBindViewHolder(final ViewHolder holder, int position) {
        final String element = mDataset[position];

        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               onClickSubject.onNext(element);
            }
        });
    }

    public Observable<String> getPositionClicks(){
        return onClickSubject.asObservable();
    }
}

原帖:
自引入ListView 以来,onItemClickListener一直存在问题。当你有任何内部元素的点击监听器的时候,回调将不会被触发,但它没有被通知或没有很好的文档记录(如果有的话),所以有很多混淆和SO问题。

鉴于RecyclerView更进一步,没有行/列的概念,而是任意布局的子项数量,他们已经将onClick委托给它们中的每一个,或者程序员实现。

Recyclerview看作是一个 ListView 替代品,而不是一个更复杂用例的灵活组件。正如你所说,你的解决方案是谷歌期望你做的。现在你有一个适配器可以将 onClick 委派给在构造函数中传递的接口,这是ListViewRecyclerview的正确模式。

public static class ViewHolder extends RecyclerView.ViewHolder implements OnClickListener {

    public TextView txtViewTitle;
    public ImageView imgViewIcon;
    public IMyViewHolderClicks mListener;

    public ViewHolder(View itemLayoutView, IMyViewHolderClicks listener) {
        super(itemLayoutView);
        mListener = listener;
        txtViewTitle = (TextView) itemLayoutView.findViewById(R.id.item_title);
        imgViewIcon = (ImageView) itemLayoutView.findViewById(R.id.item_icon);
        imgViewIcon.setOnClickListener(this);
        itemLayoutView.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        if (v instanceof ImageView){
           mListener.onTomato((ImageView)v);
        } else {
           mListener.onPotato(v);
        }
    }

    public static interface IMyViewHolderClicks {
        public void onPotato(View caller);
        public void onTomato(ImageView callerImage);
    }

}

然后在你的适配器上

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

   String[] mDataset = { "Data" };

   @Override
   public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
       View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_layout, parent, false);

       MyAdapter.ViewHolder vh = new ViewHolder(v, new MyAdapter.ViewHolder.IMyViewHolderClicks() { 
           public void onPotato(View caller) { Log.d("VEGETABLES", "Poh-tah-tos"); };
           public void onTomato(ImageView callerImage) { Log.d("VEGETABLES", "To-m8-tohs"); }
        });
        return vh;
    }

    // Replace the contents of a view (invoked by the layout manager) 
    @Override 
    public void onBindViewHolder(ViewHolder holder, int position) {
        // Get element from your dataset at this position 
        // Replace the contents of the view with that element 
        // Clear the ones that won't be used
        holder.txtViewTitle.setText(mDataset[position]);
    } 

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

现在查看最后一段代码:onCreateViewHolder(ViewGroup parent,int viewType)签名已经提示了不同的视图类型。对于他们中的每一个,您都需要不同的查看者,随后他们每个人都可以拥有不同的点击次数。或者你可以创建一个通用的视图,它可以获取任何视图和一个onClickListener并相应地应用。或者将一个级别委派给协调器,以便多个片段/活动具有相同的具有不同点击行为的列表。再一次,所有的灵活性都在你身边。

它是一个非常需要的组件,并且与我们迄今为止对ListView`的内部实现和改进非常接近。谷歌最终承认它是很好的。


#2 热门回答(79 赞)

我喜欢这种方式,我正在使用它

public Adapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)

View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_image_and_text, parent, false);
v.setOnClickListener(new MyOnClickListener());

并在你想要的地方创建这个类

class MyOnClickListener implements View.OnClickListener {
    @Override
    public void onClick(View v) {
       int itemPosition = recyclerView.indexOfChild(v);
       Log.e("Clicked and Position is ",String.valueOf(itemPosition));
    }
}

我之前读过的有更好的方法,但我喜欢这种方式很简单,并不复杂。


#3 热门回答(78 赞)

另一种解决方案是Android GDE的Hugo Visser oneproposed。他为您提供免授权课程,只需放入您的代码并使用它即可。

用法:

ItemClickSupport.addTo(mRecyclerView)
        .setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
    @Override
    public void onItemClicked(RecyclerView recyclerView, int position, View v) {
        // do it
    }
});

(它也支持长时间点击)

实施(由我添加的评论):

public class ItemClickSupport {
    private final RecyclerView mRecyclerView;
    private OnItemClickListener mOnItemClickListener;
    private OnItemLongClickListener mOnItemLongClickListener;
    private View.OnClickListener mOnClickListener = new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (mOnItemClickListener != null) {
                // ask the RecyclerView for the viewHolder of this view.
                // then use it to get the position for the adapter
                RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
                mOnItemClickListener.onItemClicked(mRecyclerView, holder.getAdapterPosition(), v);
            }
        }
    };
    private View.OnLongClickListener mOnLongClickListener = new View.OnLongClickListener() {
        @Override
        public boolean onLongClick(View v) {
            if (mOnItemLongClickListener != null) {
                RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
                return mOnItemLongClickListener.onItemLongClicked(mRecyclerView, holder.getAdapterPosition(), v);
            }
            return false;
        }
    };
    private RecyclerView.OnChildAttachStateChangeListener mAttachListener
            = new RecyclerView.OnChildAttachStateChangeListener() {
        @Override
        public void onChildViewAttachedToWindow(View view) {
            // every time a new child view is attached add click listeners to it
            if (mOnItemClickListener != null) {
                view.setOnClickListener(mOnClickListener);
            }
            if (mOnItemLongClickListener != null) {
                view.setOnLongClickListener(mOnLongClickListener);
            }
        }

        @Override
        public void onChildViewDetachedFromWindow(View view) {

        }
    };

    private ItemClickSupport(RecyclerView recyclerView) {
        mRecyclerView = recyclerView;
        // the ID must be declared in XML, used to avoid
        // replacing the ItemClickSupport without removing
        // the old one from the RecyclerView
        mRecyclerView.setTag(R.id.item_click_support, this);
        mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
    }

    public static ItemClickSupport addTo(RecyclerView view) {
        // if there's already an ItemClickSupport attached
        // to this RecyclerView do not replace it, use it
        ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
        if (support == null) {
            support = new ItemClickSupport(view);
        }
        return support;
    }

    public static ItemClickSupport removeFrom(RecyclerView view) {
        ItemClickSupport support = (ItemClickSupport) view.getTag(R.id.item_click_support);
        if (support != null) {
            support.detach(view);
        }
        return support;
    }

    public ItemClickSupport setOnItemClickListener(OnItemClickListener listener) {
        mOnItemClickListener = listener;
        return this;
    }

    public ItemClickSupport setOnItemLongClickListener(OnItemLongClickListener listener) {
        mOnItemLongClickListener = listener;
        return this;
    }

    private void detach(RecyclerView view) {
        view.removeOnChildAttachStateChangeListener(mAttachListener);
        view.setTag(R.id.item_click_support, null);
    }

    public interface OnItemClickListener {

        void onItemClicked(RecyclerView recyclerView, int position, View v);
    }

    public interface OnItemLongClickListener {

        boolean onItemLongClicked(RecyclerView recyclerView, int position, View v);
    }
}

还要创建一个values / ids.xml文件并将其放入:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <item name="item_click_support" type="id" />
</resources>

这个类通过将RecyclerView.OnChildAttachStateChangeListener附加到RecyclerView来工作。每次儿童与“RecyclerView”连接或分离时都会通知该监听器。该代码使用这个将点击/长按听众附加到视图。该监听器向RecyclerView询问包含该位置的RecyclerView.ViewHolder

如果您需要更多,您也可以调整代码,让您回到持有人身上。

请记住,通过在列表中的每个视图上设置一个点击监听器,就像在其他答案中一样,在您的适配器中处理它是完全正确的。这不是最有效的做法(每次重新使用视图时都会创建一个新的侦听器),但它可以工作,而且在大多数情况下这不是问题。
关于为什么RecyclerView没有onItemClickListener
RecyclerView是一个工具箱,与之前的ListView相比,它具有更少的特性和更多的灵活性。 onItemClickListener不是从ListView中删除的唯一功能。但它有很多听众和方法可以根据自己的喜好进行扩展,它在正确的手中更加强大)。

在我看来,RecyclerView中删除的最复杂的功能是Fast Scroll。大多数其他功能可以很容易地重新实现。