眾所周知,RecyclerView是繼承自ViewGroup的,而不是像ListView一樣繼承自AbsListview.所以RecyclerView沒有OnItemClickListener,沒有OnItemLongClickListener,更沒有OnItemSelectedListener.所以這都要我們自己實(shí)現(xiàn)。What?。。?Are you kidding me? 嚴(yán)肅臉,沒錯(cuò),就是要我們自己來實(shí)現(xiàn)。實(shí)現(xiàn)的方法有很多種,我這里做了一下總結(jié),接下來我們一一列舉出來并且對(duì)比一下,當(dāng)然最后選哪一個(gè)還是看你自己喜歡咯。
- 修改RecyclerView的源碼,在ViewHolder里面添加監(jiān)聽。
- 首先在RecyclerView里面添加
OnItemClickListenr接口,并且添加OnItemClickListener的成員變量以及set方法如下:
private OnItemClickListener onItemClickListener;
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
public void setOnItemClickListener(OnItemClickListener listener) {
mOnItemClickListener = listener;
}
然后再RecyclerView的抽象類ViewHolder里面的構(gòu)造方法里面添加如下代碼。
public ViewHolder(View itemView) {
if (itemView == null) {
throw new IllegalArgumentException("itemView may not be null");
}
this.itemView = itemView;
this.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//getChildLayoutPosition(v),根據(jù)v來獲取v的位置。 mOnItemClickListener.onItemClick(v, getChildLayoutPosition(v))
}
}); //添加這一句就可以添加onclick事件了。
}
這個(gè)方法雖然可行,但是需要修改RecyclerView的源碼,在ViewHolder的構(gòu)造函數(shù)這里直接添加onclicklistener只能對(duì)整個(gè)item設(shè)置click事件,不能對(duì)item里面的子布局設(shè)置click響應(yīng)事件。我不推薦這種做法,破壞了RecyclerView的封裝性。只是在這里提一下,多提供一種思路。
- 不修改源碼,在適配器設(shè)置OnItemClickListener
不多說,上代碼。
//ReclcyerView 的適配器
class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.MyHomeViewHolder> {
private List<String> mData;
public HomeAdapter(List<String> data) {
super();
mData = data;
}
@Override
public MyHomeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//加載布局
MyHomeViewHolder viewHolder = new MyHomeViewHolder(LayoutInflater.from(MainActivity.this).inflate(R.layout.view_item, parent, false));
return viewHolder;
}
@Override
public void onBindViewHolder(final MyHomeViewHolder holder, final int position) {
//onBindViewHolder 初始化布局
holder.mNum.setText(mData.get(position));
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "onclick" + position, Toast.LENGTH_LONG).show();
addData(holder.getLayoutPosition());
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener(){
@Override
public boolean onLongClick(View v) {
Toast.makeText(MainActivity.this, "on long click" + position, Toast.LENGTH_LONG).show();
removeData(holder.getLayoutPosition());
return true;
}
});
}
@Override
public int getItemCount() {
return mData.size();
}
class MyHomeViewHolder extends RecyclerView.ViewHolder {
TextView mNum;
public MyHomeViewHolder(View itemView) {
super(itemView);
//ViewHolder查找布局
mNum = (TextView) itemView.findViewById(R.id.txt_num);
}
}
}
如上代碼,在onBindViewHolder方法中,我們通過viewholder獲取到item中的布局,對(duì)item中的設(shè)置響應(yīng)的點(diǎn)擊事件。相對(duì)于修改源碼的來說,這個(gè)可以對(duì)item中的view的點(diǎn)擊事件進(jìn)行設(shè)置。注意父布局搶占子布局焦點(diǎn)的問題。記得設(shè)置mRecyclerView.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS)這樣父布局焦點(diǎn)在子布局獲取焦點(diǎn)之后。
同樣的你也可以給Apdater添加OnItemListener回調(diào)接口以及成員變量,通過構(gòu)造函數(shù)或者set方法設(shè)置回調(diào),這樣就可以將onClick的處理從adapter里面抽離出去。
- 實(shí)現(xiàn) RecyclerView.OnItemTouchListener
實(shí)現(xiàn)RecyclerView.OnItemTouchListener監(jiān)聽RecyclerView的touch事件,通過捕獲touch事件,根據(jù)event的x,y以及RecyclerView的findChildViewUnder(e.getX(),e.getY())來獲取到當(dāng)前被觸摸的view。然后利用手勢(shì)來判斷是長按還是點(diǎn)擊,從而回調(diào)相應(yīng)的回調(diào)函數(shù)。
示例代碼如下:
public class RecyclerViewClickListener implements RecyclerView.OnItemTouchListener {
private GestureDetector mGestureDetector;
private OnItemClickListener mListener;
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
public RecyclerViewClickListener(Context context, final RecyclerView recyclerView,OnItemClickListener listener){
mListener = listener;
mGestureDetector = new GestureDetector(context,
new GestureDetector.SimpleOnGestureListener(){
//click
@Override
public boolean onSingleTapUp(MotionEvent e) {
View childView = recyclerView.findChildViewUnder(e.getX(),e.getY());
if(childView != null && mListener != null){
mListener.onItemClick(childView,recyclerView.getChildLayoutPosition(childView));
return true;
}
return false;
}
//long click
@Override
public void onLongPress(MotionEvent e) {
View childView = recyclerView.findChildViewUnder(e.getX(),e.getY());
if(childView != null && mListener != null){
mListener.onItemLongClick(childView,recyclerView.getChildLayoutPosition(childView));
}
}
});
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
if(mGestureDetector.onTouchEvent(e)){
return true;
}else
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
Log.i("doris", "onTouchEvent");
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
Log.i("doris", "onRequestDisallowInterceptTouchEvent");
}
}
其實(shí)不通過手勢(shì)來判斷也是可以的,那就是監(jiān)聽KEY_DOWN以及KEY_MOVE,KEY_UP,根據(jù)動(dòng)作間隔以及移動(dòng)的具體來判斷是點(diǎn)擊還是滑動(dòng)還是長按。實(shí)現(xiàn)方法可參考揭開RecyclerView的神秘面紗(二):處理RecyclerView的點(diǎn)擊事件.不過與其自己計(jì)算不如用google已經(jīng)封裝好的,這樣可以更精確的判斷點(diǎn)擊以及長按事件,避免了由于頻繁快速的操作導(dǎo)致計(jì)算錯(cuò)誤的情況。
以為這就完了? 不不不,還有辦法呢,而且我個(gè)人比較喜歡這種方法。因?yàn)楹唵危肭中圆淮?,不用修改源碼,只需設(shè)置回調(diào)接口,就可以方便的使用了。準(zhǔn)備好了嗎? 我要放代碼啦!?。?!
- 重寫RecyclerView的onChildAttachedToWindow方法
首先讓我們一起來看看onChildAttachedToWindow方法:
/**
* Called when an item view is attached to this RecyclerView.
*
* <p>Subclasses of RecyclerView may want to perform extra bookkeeping or modifications
* of child views as they become attached. This will be called before a
* {@link LayoutManager} measures or lays out the view and is a good time to perform these
* changes.</p>
*
* @param child Child view that is now attached to this RecyclerView and its associated window
*/
public void onChildAttachedToWindow(View child) {
}
以我蹩腳的英語水平看了一下英文注釋,這個(gè)方法就是在itemView要被attach(關(guān)聯(lián))到RecylclerView的時(shí)候調(diào)用,RecyclerView的子布局可以重寫這個(gè)方法,從而在View要被``attach的時(shí)候?qū)?/code>itemView做一些操作。這個(gè)方法LayoutManager`繪制view之前調(diào)用,所以如果你想對(duì)view做特殊的處理,這個(gè)方法是一個(gè)很好的切入口。果然GOOGLE的開發(fā)人員還是很有愛的,一早就給我們預(yù)留了方便我們拓展的方法,機(jī)智如你啦。
好吧,看完了上面的這個(gè)方法的解說,你的大腦可以快速運(yùn)轉(zhuǎn)了,這說明了什么?我可以利用這個(gè)干什么? 好吧,謎底揭曉,既然可以在這里操作到每一個(gè)view,那我們就可以在這里對(duì)view進(jìn)行事件監(jiān)聽的設(shè)置啦,不是嗎不是嗎? 對(duì)對(duì)對(duì),沒錯(cuò)。不過呢,這個(gè)方法也只能對(duì)整個(gè)item的view進(jìn)行設(shè)置,所以如果你的item沒有多個(gè)button的話,其實(shí)用這個(gè)方法是很不錯(cuò)的。話不多說,又到了貼代碼的時(shí)候了。有沒有那么一丟丟的小期待,haa!
//增加一個(gè)私有的ItemListener
private interface ItemListener extends OnClickListener, OnFocusChangeListener, OnKeyListener {
}
//在構(gòu)造函數(shù)里創(chuàng)建該對(duì)象,并重寫方法如下
mItemListener = new ItemListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if(null != mOnItemOnKeyListener){
mOnItemOnKeyListener.onKey(v,keyCode,event);
}
return false;
}
/**
* 子控件的點(diǎn)擊事件
* @param itemView
*/
@Override
public void onClick(View itemView) {
if (null != mOnItemClickListener) {
mOnItemClickListener.onItemClick(RecyclerViewTV.this, itemView, getChildLayoutPosition(itemView));
}
}
/**
* 子控件的焦點(diǎn)變動(dòng)事件
* @param itemView
* @param hasFocus
*/
@Override
public void onFocusChange(View itemView, boolean hasFocus) {
if (null != mOnItemListener) {
if (null != itemView) {
mItemView = itemView; // 選中的item.
itemView.setSelected(hasFocus);
if (hasFocus) {
mCurrentSelectView = mItemView;
mOnItemListener.onItemSelected(RecyclerViewTV.this, itemView, getChildLayoutPosition(itemView));
} else {
mOnItemListener.onItemPreSelected(RecyclerViewTV.this, itemView, getChildLayoutPosition(itemView));
}
}
}
}
};
onChildAttachedToWindow的重寫方法如下:
@Override
public void onChildAttachedToWindow(View child) {
if (!child.hasOnClickListeners()) {
child.setOnClickListener(mItemListener);
}
if (child.getOnFocusChangeListener() == null) {
child.setOnFocusChangeListener(mItemListener);
}
child.setOnKeyListener(mItemListener);
}
這個(gè)代碼是引用自androidtvwidget大家可以在github里面搜索,里面有recyclerView的封裝,用起來還是不錯(cuò)的,而且是Android TV,當(dāng)然如果你不是Android TV的開發(fā)者,一樣也可以修改一下用于手機(jī)端的。
好了,到這里我們的RecyclerView如何設(shè)置OnItmeClick事件的方法匯總就完了。如果還有發(fā)現(xiàn)其他更好的辦法,我會(huì)更新進(jìn)來。
喜歡我的匯總的可以點(diǎn)個(gè)贊,你們的每一個(gè)點(diǎn)贊都是對(duì)我學(xué)習(xí)路上的莫大的鼓勵(lì),當(dāng)然歡迎大家留言交流,共同進(jìn)步。
最后貼出參考鏈接,這幾篇都是我覺得還不錯(cuò)的資源:
揭開RecyclerView的神秘面紗(二):處理RecyclerView的點(diǎn)擊事件
https://stackoverflow.com/questions/24885223/why-doesnt-recyclerview-have-onitemclicklistener
https://stackoverflow.com/questions/24471109/recyclerview-onclick/26196831#26196831