协慌网

登录 贡献 社区

为什么 RecyclerView 没有 onItemClickListener()?

我正在探索RecyclerView ,我很惊讶地发现RecyclerView没有onItemClickListener() 。因为RecyclerView扩展了

android.view.ViewGroup

ListView扩展

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) {

    }
}

但我还是想知道为什么谷歌删除了onItemClickListener()

是否有性能问题或其他问题?

答案

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 1:1 替代品,而不是复杂用例的更灵活组件。正如你所说,你的解决方案是谷歌对你的期望。现在你有了一个适配器,可以将 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内部实现和改进非常接近。谷歌最终承认这一点很好。

另一种解决方案是由 Android GDE 的Hugo Visser 提出的解决方案。他为您提供了免费许可课程,您只需输入代码并使用它即可。

用法:

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.OnChildAttachStateChangeListenerRecyclerView 。每次从RecyclerView连接或分离子项时,都会通知此侦听器。代码使用此选项将点击 / 长按侦听器附加到视图。该监听器向RecyclerView询问包含该位置的RecyclerView.ViewHolder

如果您需要更多,您还可以调整代码以便为您提供支持。

请记住,通过在列表的每个视图上设置一个点击监听器,就像其他建议的答案一样,在您的适配器中处理它完全没问题。这不是最有效的事情(每次重复使用视图时都会创建一个新的侦听器),但它可以工作,在大多数情况下它不是问题。

关于为什么 RecyclerView没有onItemClickListener

RecyclerView是一个工具箱,与旧的ListView相比,它具有较少的功能构建和更大的灵活性。 onItemClickListener不是从 ListView 中删除的唯一功能。但它有很多听众和方法可以根据自己的喜好扩展它,它在右手中更强大;)。

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

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

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));
    }
}

我以前读过有更好的方法,但我喜欢这种方式很容易而且不复杂。