自從開始使用 RecyclerView 代替 ListView ,會發(fā)現(xiàn)有很多地方需要學(xué)習(xí).前一段時間的學(xué)習(xí)記錄有:
RecyclerView的滾動事件研究 - DevWiki
RecyclerView的ViewHolder和Adapter的封裝優(yōu)化 - DevWiki
RecyclerView問題記錄 - DevWiki
今天來學(xué)習(xí)一下如何實現(xiàn) RecyclerView 的Item的點擊事件.實現(xiàn)Item的點擊事件有三種方式:
通過 RecyclerView 已有的方法 addOnItemTouchListener() 實現(xiàn)
在創(chuàng)建 ItemView 時添加點擊監(jiān)聽
當(dāng) ItemView attach RecyclerView 時實現(xiàn)
- 通過 RecyclerView 的 addOnItemTouchListener() 實現(xiàn)
1.1 查看源碼
查看 RecyclerView 源碼可以看到, RecyclerView 預(yù)留了一個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);
}
通過注釋我們可知,此方法是在滾動事件之前調(diào)用.需要傳入一個 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);
}
此接口還提供了一個實現(xiàn)類,且官方推薦使用該實現(xiàn)類 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) {
}
}
在觸摸接口中,當(dāng)觸摸時會回調(diào)一個 MotionEvent 對象,通過使用 GestureDetectorCompat 來解析用戶的操作.
1.2 實現(xiàn)點擊事件監(jiān)聽
寫一個 ItemClickListener 類繼承 SimpleOnItemTouchListener ,構(gòu)造時傳入 RecyclerView 對象和Item點擊回調(diào),并覆寫父類的 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 的手勢回調(diào)中我們覆寫:
boolean onSingleTapUp(MotionEvent e)
void onLongPress(MotionEvent e)
1.3 使用事件監(jiān)聽
在 RecyclerView 的對象中添加 addOnItemTouchListener() 方法,然后在回調(diào)中處理你需要的事件:
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();
}
}));
- 在創(chuàng)建 ItemView 時添加點擊監(jiān)聽
這種方法和 ListView 一樣,在Adapter里面創(chuàng)建 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對象上添加監(jiān)聽回調(diào):
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();
}
});
- 當(dāng) ItemView attach RecyclerView 時實現(xiàn)
該實現(xiàn)方法是在閱讀國外的一篇博客時發(fā)現(xiàn)的,原文鏈接如下: Getting your clicks on RecyclerView
實現(xiàn)的代碼如下:
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 設(shè)置了 OnChildAttachStateChangeListener 事件監(jiān)聽,當(dāng)子 View attach RecyclerView 時設(shè)置事件監(jiān)聽.
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) {}
};
- 三種方式對比
以上三種方式分別是:
通過 RecyclerView 已有的方法 addOnItemTouchListener() 實現(xiàn)
在創(chuàng)建 ItemView 時添加點擊監(jiān)聽
當(dāng) ItemView attach RecyclerView 時實現(xiàn)
從以上三種方式的實現(xiàn)過程可知:
三種均可實現(xiàn) ItemView 的點擊事件和長按事件的監(jiān)聽.
第一種方式可以很方便獲取用戶點擊的坐標(biāo).
第二種和第三種方式可以很方便對 ItemView 中的子 View 進(jìn)行監(jiān)聽.
第一種方式和第三種方式可以寫在單獨的類中,相對于第二種寫在 Adapter 的方式可使代碼更獨立整潔
綜上所述:
如果你只想監(jiān)聽 ItemView 的點擊事件或長按事件,三種方式均可.
如果你想監(jiān)聽 ItemView 中每個子 View 的點擊事件,采用第二種或者第三種比較方面.