原文鏈接:http://www.littlerobots.nl/blog/Handle-Android-RecyclerView-Clicks/
RecyclerView是一個(gè)非常強(qiáng)大的組件類盈滴,相對(duì)于ListView來說你應(yīng)該考慮使用RecyclerView來構(gòu)建列表界面展現(xiàn)數(shù)據(jù)躬它。它提供了很多靈活且已內(nèi)置的回調(diào)接口讓你易于實(shí)現(xiàn)動(dòng)效功能和定制布局管理器婉刀。
不幸的是RecyclerView并沒有像ListView那樣提供當(dāng)單擊item時(shí)觸法一個(gè)OnItemClickListener事件的功能损话。RecyclerView允許你通過給Adapter傳遞一個(gè)OnClickListener對(duì)象匀钧,隨后在adapter中傳遞給ViewHolder翎碑,為了捕獲一個(gè)單擊事件這種方式稍顯復(fù)雜。
[譯者本人曾經(jīng)踩過一個(gè)坑:在給Adapter傳遞OnClickListener對(duì)象時(shí)之斯,將該字段設(shè)置成了類變量(static修飾符)隨后在ViewHolder中通過 Adapter.xxxListener 的方式調(diào)用日杈,奇葩的bug誕生了,單擊item時(shí)偶發(fā)性無響應(yīng)佑刷。解決方法* — 改成實(shí)例字段再傳遞給ViewHolder*]
幸運(yùn)的是RecyclerView提供了一個(gè)addOnItemTouchListener
來捕獲item的所有觸摸事件莉擒。你可以在OnItemTouchListener的邏輯代碼中使用GestureDetector計(jì)算出單擊、長(zhǎng)按等事件瘫絮。在你的計(jì)算代碼中稍有不慎GestureDetector會(huì)攔截觸摸事件并搞砸事情涨冀,在這種情況下item view實(shí)際上是接受不到觸摸事件以致于用戶得不到視覺和聽覺上的反饋。
下邊給出一段代碼來說明使用GestureDetector的糟糕情況麦萤,也是譯者曾經(jīng)踩過的坑
/**
* Created by artshell on 2015/5/24 0024.
* 872780008@qq.com
*/
public class PopularCityAdapter extends RecyclerView.Adapter<PopularCityAdapter.PopularCityViewHolder> {
public static final String TAG = "PopularCityAdapter";
// code ...
private OnItemClickListener mOnItemClickListener;
public interface OnItemClickListener {
/**
* @param pView child view of RecyclerView's layout
* @param pPosition {@link RecyclerView#getChildAdapterPosition(View)}
* @see {@link RecyclerView.ViewHolder#getAdapterPosition()}
*/
void onRecyclerItemClick(View pView, int pPosition);
}
/**
* @param pOnItemClickListener 單擊事件監(jiān)聽器
*/
public void setOnItemClickListener(OnItemClickListener pOnItemClickListener) {
mOnItemClickListener = pOnItemClickListener;
}
// 其它 Adapter 實(shí)現(xiàn)代碼省略 ...
public static class RecyclerViewItemClickListener implements RecyclerView.OnItemTouchListener {
private OnItemClickListener mItemClickListener;
private GestureDetector mGestureDetector;
public RecyclerViewItemClickListener(Context pContext, OnItemClickListener pItemClickListener) {
mItemClickListener = pItemClickListener;
mGestureDetector = new GestureDetector(pContext, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
return true;
}
});
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
View childView = rv.findChildViewUnder(e.getX(), e.getY());
int position = rv.getChildAdapterPosition(childView);
if (childView != null && position != RecyclerView.NO_POSITION && mItemClickListener != null && mGestureDetector.onTouchEvent(e)) {
mItemClickListener.onRecyclerItemClick(childView, position);
}
return false; /* 這里應(yīng)該返回false,應(yīng)該讓事件繼續(xù)傳遞下去,否則只是處理了Touch事件,而click事件得不到執(zhí)行 */
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
}
我想到一個(gè)方案鹿鳖,就是通過RecyclerView item中的view,確切的說像ViewHolder.getItemView()獲取view來處理單擊事件壮莹。作者在賣關(guān)子
封裝后翅帜,像這樣:
ItemClickSupport.addTo(mRecyclerView).setOnItemClickListener(new ItemClickSupport.OnItemClickListener() {
@Override
public void onItemClicked(RecyclerView recyclerView, int position, View v) {
// do it
}
});
使用過TwoWayView這個(gè)開源項(xiàng)目的用戶可能注意到了ItemClickSupport與項(xiàng)目中的實(shí)現(xiàn)方式很像,實(shí)際上在此之前我在Bundle這個(gè)app中使用TwoWayViews時(shí)踩了些坑命满,里邊用的技術(shù)就是上邊提到的RecyclerView.OnItemTouchListener涝滴。當(dāng)我實(shí)現(xiàn)ItemClickSupport后,去查看TwoWayView 發(fā)現(xiàn)兩種實(shí)現(xiàn)方式類似胶台,我覺得TwoWayView中api的實(shí)現(xiàn)方式很優(yōu)雅而沒有多次傳遞 xxxListener歼疮。
相對(duì)于TwoWayView 中的實(shí)現(xiàn)方式我使用的是OnChildAttachStateChangeListener傳遞一個(gè)OnClickListener對(duì)象給ViewHolder中的itemView而不是定制OnTouchListener來達(dá)到目的。
代碼如下:
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);
}
}
當(dāng)你在使用這個(gè)工具類的時(shí)候需要在 ids.xml文件中定義一個(gè)R.id.item_click_support
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="item_click_support" type="id" />
</resources>
代碼可以自由使用诈唬,法律咨詢事宜單擊這兒韩脏。here
我比較喜歡使用此方法,而不是通過傳遞OnClickListeners讯榕,當(dāng)然這種情況僅僅是針對(duì)item需要處理單擊事件骤素。下次你的需求中遇到此類情況匙睹,可以考慮使用這個(gè)技術(shù)。
感謝Mike Wolfson和*Dave Smith *兩位作者對(duì)本篇文章的校審.
你可以通過Discuss this post on Google+提交你的問題济竹,評(píng)論痕檬,反饋等.