4

三种方式实现RecyclerView的Item点击事件

 2 years ago
source link: http://blog.devwiki.net/2016/07/24/three-ways-click-recyclerview-item.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

自从开始使用RecyclerView代替ListView,会发现有很多地方需要学习.前一段时间的学习记录有:

今天来学习一下如何实现RecyclerView的Item的点击事件.实现Item的点击事件有三种方式:

  1. 通过RecyclerView已有的方法addOnItemTouchListener()实现
  2. 在创建ItemView时添加点击监听
  3. ItemView attach RecyclerView时实现

1. 通过RecyclerViewaddOnItemTouchListener()实现

1.1 查看源码

查看RecyclerView源码可以看到,RecyclerView预留了一个Item的触摸事件方法:

/**
 * Add an {@link OnItemTouchListener} to intercept touch events before they are dispatched
 * to child views or this view's standard scrolling behavior.
 *
 * <p>Client code may use listeners to implement item manipulation behavior. Once a listener
 * returns true from
 * {@link OnItemTouchListener#onInterceptTouchEvent(RecyclerView, MotionEvent)} its
 * {@link OnItemTouchListener#onTouchEvent(RecyclerView, MotionEvent)} method will be called
 * for each incoming MotionEvent until the end of the gesture.</p>
 *
 * @param listener Listener to add
 * @see SimpleOnItemTouchListener
 */
public void addOnItemTouchListener(OnItemTouchListener listener) {
    mOnItemTouchListeners.add(listener);
}

通过注释我们可知,此方法是在滚动事件之前调用.需要传入一个OnItemTouchListener对象.OnItemTouchListener的代码如下:

public static interface OnItemTouchListener { 

    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);

    public void onTouchEvent(RecyclerView rv, MotionEvent e);

    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept);
}

此接口还提供了一个实现类,且官方推荐使用该实现类SimpleOnItemTouchListener

/**
 * An implementation of {@link RecyclerView.OnItemTouchListener} that has empty method bodies and
 * default return values.
 * <p>
 * You may prefer to extend this class if you don't need to override all methods. Another
 * benefit of using this class is future compatibility. As the interface may change, we'll
 * always provide a default implementation on this class so that your code won't break when
 * you update to a new version of the support library.
 */
public static class SimpleOnItemTouchListener implements RecyclerView.OnItemTouchListener {
    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        return false;
    }

    @Override
    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    }
}

在触摸接口中,当触摸时会回调一个MotionEvent对象,通过使用GestureDetectorCompat来解析用户的操作.

1.2 实现点击事件监听

写一个ItemClickListener类继承SimpleOnItemTouchListener,构造时传入RecyclerView对象和Item点击回调,并覆写父类的boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e)方法,具体代码如下:

/**
 * 点击事件
 * Created by DevWiki on 2016/7/16.
 */

public class ItemClickListener extends RecyclerView.SimpleOnItemTouchListener {

    private OnItemClickListener clickListener;
    private GestureDetectorCompat gestureDetector;

    public interface OnItemClickListener {

        void onItemClick(View view, int position);

        void onItemLongClick(View view, int position);
    }

    public ItemClickListener(final RecyclerView recyclerView,
                             OnItemClickListener listener) {
        this.clickListener = listener;
        gestureDetector = new GestureDetectorCompat(recyclerView.getContext(),
                new GestureDetector.SimpleOnGestureListener() {
                    @Override
                    public boolean onSingleTapUp(MotionEvent e) {
                        View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
                        if (childView != null && clickListener != null && gestureDetector.onTouchEvent(e)) {
                            clickListener.onItemClick(childView, recyclerView.getChildAdapterPosition(childView));
                        }
                        return true;
                    }

                    @Override
                    public void onLongPress(MotionEvent e) {
                        View childView = recyclerView.findChildViewUnder(e.getX(), e.getY());
                        if (childView != null && clickListener != null) {
                            clickListener.onItemLongClick(childView,
                                    recyclerView.getChildAdapterPosition(childView));
                        }
                    }
                });
    }

    @Override
    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
        gestureDetector.onTouchEvent(e);
        return false;
    }
}

GestureDetectorCompat的手势回调中我们覆写:

  1. boolean onSingleTapUp(MotionEvent e)单击抬起回调
  2. void onLongPress(MotionEvent e)长按回调

1.3 使用事件监听

RecyclerView的对象中添加addOnItemTouchListener()方法,然后在回调中处理你需要的事件:

recyclerView.addOnItemTouchListener(new SingleItemClickListener(recyclerView,
        new SingleItemClickListener.OnItemClickListener() {

            @Override
            public void onItemClick(View view, int position) {
                DevLog.i("touch click name:" + position);
                Toast.makeText(SingleActivity.this, "touch click:" + position, Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onItemLongClick(View view, int position) {
                DevLog.i("touch long click:" + position);
                Toast.makeText(SingleActivity.this, "touch long click:" + position, Toast.LENGTH_SHORT).show();
            }
        }));

2. 在创建ItemView时添加点击监听

这种方法和ListView一样,在Adapter里面创建View时添加点击事件.比如:

@Override
public void bindCustomViewHolder(SingleHolder holder, final int position) {
    Person person = getItem(position);
    holder.nameView.setText(person.getName());
    holder.ageView.setText(String.valueOf(person.getAge()));

    if (clickListener != null) {
        holder.nameView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                clickListener.onNameClick(position);
            }
        });
        holder.ageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                clickListener.onAgeClick(position);
            }
        });
    }
}

然后在Adapter对象上添加监听回调:

singleAdapter.setClickListener(new SingleAdapter.OnSingleItemClickListener() {
    @Override
    public void onNameClick(int position) {
        DevLog.i("adapter click name:" + position);
        Toast.makeText(SingleActivity.this, "adapter click name:" + position, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onAgeClick(int position) {
        DevLog.i("adapter click age:" + position);
        Toast.makeText(SingleActivity.this, "adapter click name:" + position, Toast.LENGTH_SHORT).show();
    }
});

3. 当ItemView attach RecyclerView时实现

该实现方法是在阅读国外的一篇博客时发现的,原文链接如下:Getting your clicks on RecyclerView

实现的代码如下:

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) {
                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) {
            if (mOnItemClickListener != null) {
                view.setOnClickListener(mOnClickListener);
            }
            if (mOnItemLongClickListener != null) {
                view.setOnLongClickListener(mOnLongClickListener);
            }
        }

        @Override
        public void onChildViewDetachedFromWindow(View view) {}
    };

    private ItemClickSupport(RecyclerView recyclerView) {
        mRecyclerView = recyclerView;
        mRecyclerView.setTag(R.id.item_click_support, this);
        mRecyclerView.addOnChildAttachStateChangeListener(mAttachListener);
    }

    public static ItemClickSupport addTo(RecyclerView view) {
        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 事件监听,当子View attach RecyclerView时设置事件监听.

    private RecyclerView.OnChildAttachStateChangeListener mAttachListener
            = new RecyclerView.OnChildAttachStateChangeListener() {
        @Override
        public void onChildViewAttachedToWindow(View view) {
            if (mOnItemClickListener != null) {
                view.setOnClickListener(mOnClickListener);
            }
            if (mOnItemLongClickListener != null) {
                view.setOnLongClickListener(mOnLongClickListener);
            }
        }

        @Override
        public void onChildViewDetachedFromWindow(View view) {}
    };

4. 三种方式对比

以上三种方式分别是:

  1. 通过RecyclerView已有的方法addOnItemTouchListener()实现
  2. 在创建ItemView时添加点击监听
  3. ItemView attach RecyclerView时实现

从以上三种方式的实现过程可知:

  1. 三种均可实现ItemView的点击事件和长按事件的监听.
  2. 第一种方式可以很方便获取用户点击的坐标.
  3. 第二种和第三种方式可以很方便对ItemView中的子View进行监听.
  4. 第一种方式和第三种方式可以写在单独的类中,相对于第二种写在Adapter的方式可使代码更独立整洁

综上所述:

如果你只想监听ItemView的点击事件或长按事件,三种方式均可.

如果你想监听ItemView中每个子View的点击事件,采用第二种或者第三种比较方面.

以上三种方式均为在学习RecyclerView过程中学到的,项目地址如下:Dev-Wiki/RecyclerView

如果有错误请指正,如果你有更好的方式实现,请留言或者联系我,谢谢!

您也可关注我的公共账号,获取最新文章:

最后修改:2017 年 05 月 29 日
如果觉得我的文章对你有用,请随意赞赏

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK