为什么RecyclerView没有onItemClickListener()方法?

1046

我正在研究RecyclerView,惊讶地发现RecyclerView没有onItemClickListener()

我有两个问题。

主要问题

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

次要问题

我通过在我的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) {

    }
}

这样可以吗/有更好的方法吗?


31
为了让您的代码正常工作,您需要在ViewHolder构造函数中添加itemLayoutView.setOnClickListener(this);。 - whitneyland
3
你问了一个好问题... 许多开发人员对 RecyclerView 和 ListView 都有同样的疑惑。 - venkat
21
为什么RecyclerView没有OnItemClickListner()?除了"如何为RecyclerView添加onclick"的答案之外,以下所有答案。 - Manohar
4
希望有人能抽出时间来回答“问题”。 - Ojonugwa Jude Ochalifu
1
我认为他们删除了它是因为性能和垃圾内存问题。但是看看被接受的答案...它会产生相同的问题。我认为你在接受它时犯了错误,现在其他人也因为你而犯错 :)。 - Alex
显示剩余5条评论
31个回答

1246

简述:使用RxJava和PublishSubject公开一个可观察的点击事件。

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 替代品,而是更灵活的组件,适用于复杂的用例。正如你所说,你的解决方案是 Google 所期望的。现在你有了一个可以将 onClick 委托给在构造函数中传递的接口的适配器,这是 `ListView` 和 `Recyclerview` 的正确模式。
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),函数签名已经表明了不同的视图类型。对于每个视图类型,您都需要一个不同的viewholder,并且随后每个视图类型可以具有不同的点击集。或者您可以创建一个通用的viewholder,它接受任何视图和一个onClickListener并相应地应用。或者向编排器委派一级,以便多个片段/活动具有具有不同单击行为的相同列表。再次强调,所有的灵活性都在您这边。
这是一个非常必要的组件,与我们内部实现和改进ListView的方式非常接近。很高兴Google终于承认它的价值。

80
RecyclerView.ViewHolder 有一个 getPosition() 方法。通过对这段代码进行一些调整,你可以将位置传递给监听器。 - se_bastiaan
13
他完全错过了 onBindViewHolder(holder, position) 这个方法,而这个方法正是用来填充信息的。 - MLProgrammer-CiM
26
@se_bastiaan getPosition()已过时。"由于适配器更新的异步处理方式导致其含义不明确,请根据您的使用情况使用{@link #getLayoutPosition()}或{@link #getAdapterPosition()}。" - upenpat
8
你有几个选择。在viewholder内部放置一个int字段,绑定时更新它,然后在单击时返回它是其中之一。 - MLProgrammer-CiM
8
你有几个选项。将位置放在ViewHolder内的int字段中,在绑定时更新它,然后在点击时返回它是其中之一。ViewHolder已经具有这个值,并且已经设置好了。只需要调用viewHolder.getPosition();即可。 - Chad Bingham
显示剩余55条评论

135

为什么RecyclerView没有onItemClickListener

RecyclerView是一个工具箱,与旧的ListView相比,它具有更少的内置功能和更高的灵活性。从ListView中删除了onItemClickListener不是唯一的特性。但是它有很多监听器和方法可以扩展到您喜欢的程度,在正确的手中更加强大;)。

在我看来,RecyclerView中删除的最复杂的功能是快速滚动。大多数其他功能都可以轻松地重新实现。

如果您想知道RecyclerView添加了哪些其他酷功能,请阅读this回答另一个问题。

内存高效 - onItemClickListener的即插即用解决方案

这个解决方案是由安卓GDE Hugo Visser在RecyclerView发布后立即提出的proposed。他提供了一个免费的许可类,供您放入代码并使用。

它展示了一些由RecyclerView引入的多功能性,通过使用RecyclerView.OnChildAttachStateChangeListener实现。 < p > 2019编辑: kotlin版由我编写,java版本由Hugo Visser提供,下面保留

Kotlin / Java

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

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

请将以下代码添加到您的源代码中:

Kotlin

用法:

recyclerView.onItemClick { recyclerView, position, v ->
    // do it
}

(I also support long item click, and see below for another feature I've added.)
实现方式 (我对Hugo Visser的Java代码进行了改编):
typealias OnRecyclerViewItemClickListener = (recyclerView: RecyclerView, position: Int, v: View) -> Unit
typealias OnRecyclerViewItemLongClickListener = (recyclerView: RecyclerView, position: Int, v: View) -> Boolean

class ItemClickSupport private constructor(private val recyclerView: RecyclerView) {

    private var onItemClickListener: OnRecyclerViewItemClickListener? = null
    private var onItemLongClickListener: OnRecyclerViewItemLongClickListener? = null

    private val attachListener: RecyclerView.OnChildAttachStateChangeListener = object : RecyclerView.OnChildAttachStateChangeListener {
        override fun onChildViewAttachedToWindow(view: View) {
            // every time a new child view is attached add click listeners to it
            val holder = this@ItemClickSupport.recyclerView.getChildViewHolder(view)
                    .takeIf { it is ItemClickSupportViewHolder } as? ItemClickSupportViewHolder

            if (onItemClickListener != null && holder?.isClickable != false) {
                view.setOnClickListener(onClickListener)
            }
            if (onItemLongClickListener != null && holder?.isLongClickable != false) {
                view.setOnLongClickListener(onLongClickListener)
            }
        }

        override fun onChildViewDetachedFromWindow(view: View) {

        }
    }

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

    companion object {
        fun addTo(view: RecyclerView): ItemClickSupport {
            // if there's already an ItemClickSupport attached
            // to this RecyclerView do not replace it, use it
            var support: ItemClickSupport? = view.getTag(R.id.item_click_support) as? ItemClickSupport
            if (support == null) {
                support = ItemClickSupport(view)
            }
            return support
        }

        fun removeFrom(view: RecyclerView): ItemClickSupport? {
            val support = view.getTag(R.id.item_click_support) as? ItemClickSupport
            support?.detach(view)
            return support
        }
    }

    private val onClickListener = View.OnClickListener { v ->
        val listener = onItemClickListener ?: return@OnClickListener
        // ask the RecyclerView for the viewHolder of this view.
        // then use it to get the position for the adapter
        val holder = this.recyclerView.getChildViewHolder(v)
        listener.invoke(this.recyclerView, holder.adapterPosition, v)
    }

    private val onLongClickListener = View.OnLongClickListener { v ->
        val listener = onItemLongClickListener ?: return@OnLongClickListener false
        val holder = this.recyclerView.getChildViewHolder(v)
        return@OnLongClickListener listener.invoke(this.recyclerView, holder.adapterPosition, v)
    }

    private fun detach(view: RecyclerView) {
        view.removeOnChildAttachStateChangeListener(attachListener)
        view.setTag(R.id.item_click_support, null)
    }

    fun onItemClick(listener: OnRecyclerViewItemClickListener?): ItemClickSupport {
        onItemClickListener = listener
        return this
    }

    fun onItemLongClick(listener: OnRecyclerViewItemLongClickListener?): ItemClickSupport {
        onItemLongClickListener = listener
        return this
    }

}

/** Give click-ability and long-click-ability control to the ViewHolder */
interface ItemClickSupportViewHolder {
    val isClickable: Boolean get() = true
    val isLongClickable: Boolean get() = true
}

// Extension function
fun RecyclerView.addItemClickSupport(configuration: ItemClickSupport.() -> Unit = {}) = ItemClickSupport.addTo(this).apply(configuration)

fun RecyclerView.removeItemClickSupport() = ItemClickSupport.removeFrom(this)

fun RecyclerView.onItemClick(onClick: OnRecyclerViewItemClickListener) {
    addItemClickSupport { onItemClick(onClick) }
}
fun RecyclerView.onItemLongClick(onLongClick: OnRecyclerViewItemLongClickListener) {
    addItemClickSupport { onItemLongClick(onLongClick) }
}

(记得还需要添加XML文件,请参见本节上面的内容)

Kotlin版本的额外功能

有时您不希望RecyclerView的所有项目都可以点击。

为了解决这个问题,我引入了ItemClickSupportViewHolder接口,您可以在ViewHolder上使用它来控制哪个项目可点击。

示例:

class MyViewHolder(view): RecyclerView.ViewHolder(view), ItemClickSupportViewHolder {
    override val isClickable: Boolean get() = false
    override val isLongClickable: Boolean get() = false
}

Java

使用方法:

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

(I also支持长项目点击)
实现(我添加的注释):
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);
    }
}

工作原理(为什么它高效)

这个类通过将RecyclerView.OnChildAttachStateChangeListener附加到RecyclerView上来工作。每当一个子视图被附加或从RecyclerView中分离时,该监听器都会收到通知。代码使用它来向视图添加点击/长按监听器。该监听器要求RecyclerView返回包含位置的RecyclerView.ViewHolder

这比其他解决方案更有效,因为它避免了为每个视图创建多个监听器,并在滚动RecyclerView时不断销毁和创建它们。

如果需要更多内容,您还可以调整代码以返回holder本身。

最后的话

请记住,在适配器中通过为列表中的每个视图设置单击监听器来处理它是完全可以的,就像其他答案提出的那样。

这只是不太高效的做法(每次重用视图时都会创建新的监听器),但它可以工作,在大多数情况下也不是问题。

这也有点违背责任分离的原则,因为适配器并不真正委托单击事件。


你的论点有误,问题不在于代码量而在于效率和责任原则。代码量并不能很好地说明解决方案的优劣,特别是当该代码是可重用且高效的时候。一旦你将该代码放入项目中(可以视为库包含),你只需要在你的RecyclerView上加入一个1行代码即可。 - Daniele Segato
2
@DanieleSegato 非常感谢你! - Mr. Octodood
1
当我看到像“ItemClickSupport”这样的词时,感觉我们走错了方向。我只想要一个简单的点击监听器,为什么它如此复杂和不明显! - Megamozg
@daniele-segato 为什么 onChildViewDetachedFromWindow 方法是空的?我应该添加这一行代码 view.setOnClickListener(null) 吗? - MML
1
@MML不需要这样做,因为没有东西会点击分离视图。 - Daniele Segato
显示剩余8条评论

96
我喜欢这种方式并正在使用它
里面
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));
    }
}

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


24
应该将MyOnClickListener转化为实例变量,因为每次使用new关键字创建一个新的实例会很浪费。 - user2968401
14
recyclerView.getChildPosition(v); 已经过时,现在请使用 recyclerView.indexOfChild(v);。 - Qadir Hussain
4
你在滚动时是否遇到问题?在我的测试中,itemPosition 似乎取决于视图的回收,因此与适配器中的完整列表相比会出错。请问这是你所遇到的问题吗? - Simon
17
如何在MyOnClickListener中获取recyclerView - guo
4
应该使用以下代码: RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(v); int itemPosition = holder.getAdapterPosition(); 而不是使用以下代码: int itemPosition = recyclerView.indexOfChild(v); - Gordon Freeman
显示剩余5条评论

41

Android Recyclerview带有onItemClickListener, 为什么我们不能尝试让它像ListView一样工作呢。

来源:链接

public class RecyclerItemClickListener implements RecyclerView.OnItemTouchListener {

private OnItemClickListener mListener;
public interface OnItemClickListener {
    public void onItemClick(View view, int position);
}
GestureDetector mGestureDetector;
public RecyclerItemClickListener(Context context, OnItemClickListener listener) {
    mListener = listener;
    mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            return true;
        }
    });
}
@Override
public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent e) {
    View childView = view.findChildViewUnder(e.getX(), e.getY());
    if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) {
        mListener.onItemClick(childView, view.getChildAdapterPosition(childView));
    }
    return false;
}

@Override
public void onTouchEvent(RecyclerView view, MotionEvent motionEvent) {
}

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

  }
}

将此设置为RecyclerView:

    recyclerView = (RecyclerView)rootView. findViewById(R.id.recyclerView);
    RecyclerView.LayoutManager mLayoutManager = new            LinearLayoutManager(getActivity());
    recyclerView.setLayoutManager(mLayoutManager);
    recyclerView.addOnItemTouchListener(
            new RecyclerItemClickListener(getActivity(), new   RecyclerItemClickListener.OnItemClickListener() {
                @Override
                public void onItemClick(View view, int position) {
                    // TODO Handle item click
                    Log.e("@@@@@",""+position);
                }
            })
    );

7
说实话,不太优美。 - vishal dharankar
@vishaldharankar 为什么不呢?我认为这是避免使用SwipeRefreshLayout 后发生焦点变化冲突的最佳方式。 - NickUnuchek
这完全是不必要的,你可以使用View.OnClickListener而不是拦截触摸。 - EpicPandaForce
这是我在SO上找到的唯一一个可靠地与ItemTouchHelper一起使用的实现。 - Norman

26

感谢 @marmor,我更新了我的回答。

我认为在 ViewHolder 类构造函数中处理 onClick(),并通过 OnItemClickListener 接口将其传递给父类是一个好的解决方案。

MyAdapter.java

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

private LayoutInflater layoutInflater;
private List<MyObject> items;
private AdapterView.OnItemClickListener onItemClickListener;

public MyAdapter(Context context, AdapterView.OnItemClickListener onItemClickListener, List<MyObject> items) {
    layoutInflater = LayoutInflater.from(context);
    this.items = items;
    this.onItemClickListener = onItemClickListener;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = layoutInflater.inflate(R.layout.my_row_layout, parent, false);
    return new ViewHolder(view);
}

@Override
public void onBindViewHolder(ViewHolder holder, int position) {
    MyObject item = items.get(position);
}

public MyObject getItem(int position) {
    return items.get(position);
}


class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    private TextView title;
    private ImageView avatar;

    public ViewHolder(View itemView) {
        super(itemView);
        title = itemView.findViewById(R.id.title);
        avatar = itemView.findViewById(R.id.avatar);

        title.setOnClickListener(this);
        avatar.setOnClickListener(this);
        itemView.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        //passing the clicked position to the parent class
        onItemClickListener.onItemClick(null, view, getAdapterPosition(), view.getId());
    }
}
}

在其他类中使用适配器:

MyFragment.java

public class MyFragment extends Fragment implements AdapterView.OnItemClickListener {

private RecyclerView recycleview;
private MyAdapter adapter;

    .
    .
    .

private void init(Context context) {
    //passing this fragment as OnItemClickListener to the adapter
    adapter = new MyAdapter(context, this, items);
    recycleview.setAdapter(adapter);
}

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
    //you can get the clicked item from the adapter using its position
    MyObject item = adapter.getItem(position);

    //you can also find out which view was clicked
    switch (view.getId()) {
        case R.id.title:
            //title view was clicked
            break;
        case R.id.avatar:
            //avatar view was clicked
            break;
        default:
            //the whole row was clicked
    }
}

}

34
滚动页面时将创建和垃圾回收大量对象,相反,您应该创建一个OnClickListener并重复使用它。 - marmor
2
当滚动时,同一视图会多次调用onBindViewHolder()。即使创建一个单独的OnClickListener也无济于事,因为每次调用onBindViewHolder()时都会重置OnClickListener。请参见@MLProgrammer-CiM的解决方案。 - vovahost
你也可以改进@MLProgrammer-CiM的解决方案,而不是为每个视图创建一个监听器对象,如下所示:MyAdapter.ViewHolder vh = new ViewHolder(v, new MyAdapter.ViewHolder.IMyViewHolderClicks() .... 你可以为所有视图创建一个单一的监听器。 - vovahost

21

> RecyclerView与ListView有何不同?

一个不同之处是,RecyclerView有一个名为LayoutManager的类,它可以帮助您管理RecyclerView,例如:

通过LinearLayoutManager实现横向纵向滚动

通过GridLayoutManager实现网格布局

通过StaggeredGridLayoutManager实现瀑布流布局

例如,对于RecyclerView的水平滚动:

LinearLayoutManager llm = new LinearLayoutManager(context);
llm.setOrientation(LinearLayoutManager.HORIZONTAL);
recyclerView.setLayoutManager(llm);

21

大家可以在主活动中使用这段代码。非常高效的方法。

RecyclerView recyclerView = (RecyclerView) findViewById(R.id.users_list);            
UsersAdapter adapter = new UsersAdapter(users, this);
recyclerView.setAdapter(adapter);
adapter.setOnCardClickListner(this);

这是您的适配器类。

public class UsersAdapter extends RecyclerView.Adapter<UsersAdapter.UserViewHolder> {
        private ArrayList<User> mDataSet;
        OnCardClickListner onCardClickListner;


        public UsersAdapter(ArrayList<User> mDataSet) {
            this.mDataSet = mDataSet;
        }

        @Override
        public UserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.user_row_layout, parent, false);
            UserViewHolder userViewHolder = new UserViewHolder(v);
            return userViewHolder;
        }

        @Override
        public void onBindViewHolder(UserViewHolder holder, final int position) {
            holder.name_entry.setText(mDataSet.get(position).getUser_name());
            holder.cardView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onCardClickListner.OnCardClicked(v, position);
                }
            });
        }

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

        @Override
        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
            super.onAttachedToRecyclerView(recyclerView);
        }


        public static class UserViewHolder extends RecyclerView.ViewHolder {
            CardView cardView;
            TextView name_entry;

            public UserViewHolder(View itemView) {
                super(itemView);
                cardView = (CardView) itemView.findViewById(R.id.user_layout);
                name_entry = (TextView) itemView.findViewById(R.id.name_entry);
             }
        }

        public interface OnCardClickListner {
            void OnCardClicked(View view, int position);
        }

        public void setOnCardClickListner(OnCardClickListner onCardClickListner) {
            this.onCardClickListner = onCardClickListner;
        }
    }

在此之后,您将在您的 Activity 中获得此重写方法。

@Override
    public void OnCardClicked(View view, int position) {
        Log.d("OnClick", "Card Position" + position);
    }

5
用户向上或向下滚动时,OnClickListener是否会被反复创建和回收,这样做是否高效? - Vinh.TV
为了获取卡片的更新位置,这个小问题可以被忽略。 - waqas ali
嗨,朋友,很好,它运行得很好。我有一个需求,需要在最初的活动中使用类别和产品。如果用户选择类别图标,我需要在对话框或活动中显示类别。如果用户选择任何类别,则基于该类别加载过滤数据到第一个活动。请帮助我解决这个问题。 - Harsha
我无法覆盖OnCardClicked方法。人们是如何投票支持这个答案的? - The_Martian
就像 Gmail 一样,如何长按项目以显示上下文操作模式,为 RecyclerView 提供单选和多选模式的删除和多个选项。 - Harsha
显示剩余5条评论

16

RecyclerView没有一个onItemClickListener,因为RecyclerView负责回收视图(惊喜!),所以被回收的视图要处理接收到的点击事件。

实际上,这使得使用变得更加容易,特别是如果你有多个可点击的项目。


无论如何,在RecyclerView项目上检测点击非常容易。您所需要做的就是定义一个接口(如果您不使用Kotlin,则只需传递lambda函数即可):

public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
    private final Clicks clicks;

    public MyAdapter(Clicks clicks) {
        this.clicks = clicks;
    }

    private List<MyObject> items = Collections.emptyList();

    public void updateData(List<MyObject> items) {
        this.items = items;
        notifyDataSetChanged(); // TODO: use ListAdapter for diffing instead if you need animations
    }

    public interface Clicks {
        void onItemSelected(MyObject myObject, int position);
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {
        private MyObject myObject;    

        public MyViewHolder(View view) {
            super(view);
            // bind views
            view.setOnClickListener((v) -> {
                int adapterPosition = getBindingAdapterPosition();
                if(adapterPosition >= 0) {
                    clicks.onItemSelected(myObject, adapterPosition);
                }
            });
        }

        public void bind(MyObject myObject) {
            this.myObject = myObject;
            // bind data to views
        }
    }
}

在 Kotlin 中相同的代码:

class MyAdapter(val itemClicks: (MyObject, Int) -> Unit): RecyclerView.Adapter<MyViewHolder>() {
    private var items: List<MyObject> = Collections.emptyList()

    fun updateData(items: List<MyObject>) {
        this.items = items
        notifyDataSetChanged() // TODO: use ListAdapter for diffing instead if you need animations
    }

    inner class MyViewHolder(val myView: View): RecyclerView.ViewHolder(myView) {
        private lateinit var myObject: MyObject

        init {
            // binds views
            myView.onClick {
                val adapterPosition = getBindingAdapterPosition()
                if(adapterPosition >= 0) {
                    itemClicks.invoke(myObject, adapterPosition)
                }
            }
        }

        fun bind(myObject: MyObject) {
            this.myObject = myObject
            // bind data to views
        }
    }
}

不需要做的事情:

1.) 你不需要手动拦截触摸事件

2.) 你不需要在子视图附加状态改变监听器上纠缠

3.) 你不需要使用RxJava中的PublishSubject/PublishRelay

只需使用一个点击监听器。


视频中没有onClick处理? - EpicPandaForce
请查看视频中的0:45,适配器中附加了一个onClickListener。 - Anurag Bhalekar
1
感谢你兄弟让这成为了可能。 - Tariq Mahmood

15

如何将所有内容放在一起的示例...

public class OrderListCursorAdapter extends CursorRecyclerViewAdapter<OrderListCursorAdapter.ViewHolder> {

private static final String TAG = OrderListCursorAdapter.class.getSimpleName();
private static final int ID_VIEW_HOLDER_ACTUAL = 0;
private static final int ID_VIEW_HOLDER = 1;

public OrderListCursorAdapter(Context context, Cursor cursor) {
    super(context, cursor);
}

public static class ViewHolderActual extends ViewHolder {
    private static final String TAG = ViewHolderActual.class.getSimpleName();
    protected IViewHolderClick listener;
    protected Button button;

    public ViewHolderActual(View v, IViewHolderClick listener) {
        super(v, listener);
        this.listener = listener;
        button = (Button) v.findViewById(R.id.orderList_item_button);
        button.setOnClickListener(this);
    }

    public void initFromData(OrderData data) {
        Log.d(TAG, "><initFromData(data=" + data + ")");
        orderId = data.getId();
        vAddressStart.setText(data.getAddressStart());
        vAddressEnd.setText(data.getAddressEnd());
    }

    @Override
    public void onClick(View view) {
        if (view instanceof Button) {
            listener.onButtonClick((Button) view, getPosition(), this);
        } else {
            super.onClick(view);
        }
    }

    public interface IViewHolderClick extends ViewHolder.IViewHolderClick {
        public void onButtonClick(Button button, int position, ViewHolder viewHolder);
    }
}

public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
    private static final String TAG = ViewHolder.class.getSimpleName();
    protected long orderId;
    protected IViewHolderClick listener;
    protected TextView vAddressStart;
    protected TextView vAddressEnd;
    protected TextView vStatus;

    public ViewHolder(View v, IViewHolderClick listener) {
        super(v);
        this.listener = listener;
        v.setOnClickListener(this);

        vAddressStart = (TextView) v.findViewById(R.id.addressStart);
        vAddressEnd = (TextView) v.findViewById(R.id.addressEnd);
        vStatus = (TextView) v.findViewById(R.id.status);
    }

    public void initFromData(OrderData data) {
        Log.d(TAG, "><initFromData(data=" + data + ")");
        orderId = data.getId();
        vAddressStart.setText(data.getAddressStart());
        vAddressEnd.setText(data.getAddressEnd());
    }

    public long getOrderId() {
        return orderId;
    }

    @Override
    public void onClick(View view) {
        listener.onCardClick(view, getPosition(), this);
    }

    public interface IViewHolderClick {
        public void onCardClick(View view, int position, ViewHolder viewHolder);
    }
}

@Override
public int getItemViewType(int position) {
    return position == 0 ? ID_VIEW_HOLDER_ACTUAL : ID_VIEW_HOLDER;
}

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    Log.d(TAG, ">>onCreateViewHolder(parent=" + parent + ", viewType=" + viewType + ")");

    ViewHolder result;

    switch (viewType) {
        case ID_VIEW_HOLDER_ACTUAL: {
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_layout_actual, parent, false);
            result = new ViewHolderActual(itemView, new ViewHolderActual.IViewHolderClick() {
                @Override
                public void onCardClick(View view, int position, ViewHolder viewHolder) {
                    Log.d(TAG, "><onCardClick(view=" + view + ", position=" + position + ", viewHolder=" + viewHolder + ")");
                    Intent intent = new Intent(view.getContext(), OrderDetailActivity.class);
                    intent.putExtra(OrderDetailActivity.ARG_ORDER_ID, viewHolder.getOrderId());
                    view.getContext().startActivity(intent);
                }

                @Override
                public void onButtonClick(Button button, int position, ViewHolder viewHolder) {
                    Log.d(TAG, "><onButtonClick(button=" + button + ", position=" + position + ", viewHolder=" + viewHolder + ")");
                    Intent intent = new Intent(button.getContext(), OrderMapActivity.class);
                    intent.putExtra(OrderMapActivity.ARG_ORDER_ID, viewHolder.getOrderId());
                    button.getContext().startActivity(intent);
                }
            });
            break;
        }
        case ID_VIEW_HOLDER:
        default: {
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.card_layout, parent, false);
            result = new ViewHolder(itemView, new ViewHolder.IViewHolderClick() {
                @Override
                public void onCardClick(View view, int position, ViewHolder viewHolder) {
                    Log.d(TAG, "><onCardClick(view=" + view + ", position=" + position + ", viewHolder=" + viewHolder + ")");
                    Intent intent = new Intent(view.getContext(), OrderDetailActivity.class);
                    intent.putExtra(OrderDetailActivity.ARG_ORDER_ID, viewHolder.getOrderId());
                    view.getContext().startActivity(intent);
                }
            });
            break;
        }
    }

    Log.d(TAG, "<<onCreateViewHolder(parent=" + parent + ", viewType=" + viewType + ")= " + result);
    return result;
}

@Override
public void onBindViewHolder(ViewHolder viewHolder, Cursor cursor) {
    Log.d(TAG, "><onBindViewHolder(viewHolder=" + viewHolder + ", cursor=" + cursor + ")");
    final OrderData orderData = new OrderData(cursor);
    viewHolder.initFromData(orderData);
}
}

15

跟进MLProgrammer-CiM出色的RxJava解决方案

消费/观察点击事件

Consumer<String> mClickConsumer = new Consumer<String>() {
        @Override
        public void accept(@NonNull String element) throws Exception {
            Toast.makeText(getApplicationContext(), element +" was clicked", Toast.LENGTH_LONG).show();
        }
    };

ReactiveAdapter rxAdapter = new ReactiveAdapter();
rxAdapter.getPositionClicks().subscribe(mClickConsumer);

RxJava 2.+

将原始的tl;dr修改为:

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

PublishSubject#asObservable()已被移除。只需返回PublishSubject,它本身就是一个Observable


如果您能解释一下,在第二行中的mClickConsumer是从哪里获取的。rxAdapter.getPositionClicks().subscribe(mClickConsumer); - bluetoothfx

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