【譯】?jī)?yōu)雅的監(jiān)聽RecyclerView Item的單擊刹前、長(zhǎng)按事件

原文鏈接: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)論痕檬,反饋等.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市送浊,隨后出現(xiàn)的幾起案子梦谜,更是在濱河造成了極大的恐慌,老刑警劉巖袭景,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唁桩,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡耸棒,警方通過查閱死者的電腦和手機(jī)荒澡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來与殃,“玉大人单山,你說我怎么就攤上這事》郏” “怎么了米奸?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)爽篷。 經(jīng)常有香客問我悴晰,道長(zhǎng),這世上最難降的妖魔是什么逐工? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任铡溪,我火速辦了婚禮,結(jié)果婚禮上泪喊,老公的妹妹穿的比我還像新娘佃却。我一直安慰自己,他們只是感情好窘俺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著复凳,像睡著了一般瘤泪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上育八,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天对途,我揣著相機(jī)與錄音,去河邊找鬼髓棋。 笑死实檀,一個(gè)胖子當(dāng)著我的面吹牛惶洲,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播膳犹,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼恬吕,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了须床?” 一聲冷哼從身側(cè)響起铐料,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎豺旬,沒想到半個(gè)月后钠惩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡族阅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年篓跛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坦刀。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡愧沟,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出求泰,到底是詐尸還是另有隱情央渣,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布渴频,位于F島的核電站芽丹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏卜朗。R本人自食惡果不足惜拔第,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望场钉。 院中可真熱鬧蚊俺,春花似錦、人聲如沸逛万。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽宇植。三九已至得封,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間指郁,已是汗流浹背忙上。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留闲坎,地道東北人疫粥。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓茬斧,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親梗逮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子项秉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,102評(píng)論 25 707
  • 簡(jiǎn)介: 提供一個(gè)讓有限的窗口變成一個(gè)大數(shù)據(jù)集的靈活視圖。 術(shù)語表: Adapter:RecyclerView的子類...
    酷泡泡閱讀 5,163評(píng)論 0 16
  • 熬過了冬库糠,迎來了春伙狐。在這萬物復(fù)蘇的季節(jié),陽光和煦瞬欧,春風(fēng)微醺贷屎,花開鳥鳴,甚是和諧艘虎。 花開的季節(jié)唉侄,身邊處處都是美麗的風(fēng)...
    許芷桉閱讀 403評(píng)論 0 2
  • 第二天,出現(xiàn)了沒有hold住的事件野建,中午回店里哄皮卡丘午睡時(shí)就吼了他属划,老公今天加班,所以我看店的同時(shí)還要看孩子候生,時(shí)...
    皮卡丘媽媽閱讀 282評(píng)論 0 1
  • 1明肮、 設(shè)置系統(tǒng)導(dǎo)航欄標(biāo)題顏色和字體大小等一些屬性 [self.navigationController.navig...
    握了根草閱讀 641評(píng)論 0 3