RecyclerView Adapter in Android, made Fast and Easy

Fastadapter使RecyclerView更加簡便高效

翻譯自文章 http://blog.grafixartist.com/recyclerview-adapter-android-made-fast-easy/

使用Android RecyclerView最麻煩的莫過于使用其[adapter][]了,如果界面再復雜一些眷唉,adapter里面需要包含多個[RecyclerView.ViewHolder][viewholder]就更復雜了予颤,這里面還不包括處理各種點擊事件,點擊拖動等等其他一些很酷的功能冬阳。如果你正為此發(fā)愁蛤虐,那么這篇文章就是為你準備的。當然如果你熟悉[RecyclerView.Adapter][adapter]的標準寫法了摩泪,但是簡單的重復寫相同的代碼是很浪費時間的,那有沒有更好的辦法呢劫谅?

[adapter]: <https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html "RecyclerView.Adapter"
[viewholder]: https://developer.android.com/reference/android/support/v7/widget/RecyclerView.ViewHolder.html

Say hello to FastAdapter !

The bullet proof, fast and easy to use adapter library, which minimizes developing time to a fraction… – Mike Penz

穩(wěn)定见坑、高效嚷掠、簡單的adapter庫,能夠有效節(jié)省開發(fā)時間

Mike Penz 不僅編寫了FastAdapter荞驴,而且還編寫了MaterialDrawerAboutLibraries兩個比較火的開源庫不皆,FastAdapter中的Demo也有集成這些功能。

FastAdapter不僅僅只是減少了你的開發(fā)時間熊楼,而且它還提供了許多非常棒的功能霹娄,是時候用FastAdapter替換原來簡單形式的RecyclerView Adapters了。點擊事件(Click listeners)鲫骗,多項選擇(Multi-selection)犬耻,過濾器(filtering),拖拽功能(drag and drop)执泰,加入表頭(headers)等等枕磁,只要你能說出來的,FastAdapter都能提供术吝。

那么计济,還等什么?上車吧排苍,騷年沦寂!

(滴~~~,學生卡L匝谩传藏!)

FastAdapter庫分為核心和擴展兩個部分,所以幔翰,在Android Studio中的build.gradle中添加如下依賴:

compile('com.mikepenz:fastadapter:1.8.2@aar') {
transitive = true
}

其余擴展包在下面的兩個庫中:(我的建議是第一個一定要加漩氨,第二個隨便)

compile 'com.mikepenz:fastadapter-extensions:1.8.0@aar'
//The tiny Materialize library used for its useful helper classes
compile 'com.mikepenz:materialize:1.0.0@aar'

最新的配置還請看FastAdapter首頁說明。

建立模型類(Creating the Model class)

用作者最喜歡的芒果為例遗增,寫個相關(guān)app

public class Mango {
    private String name, description, imageUrl;

    public Mango() { }

    public Mango(String name, String description, String imageUrl) {
        this.name = name;
        this.description = description;
        this.imageUrl = imageUrl;
    }
    // Your variable Getter Setters here
}

實現(xiàn)Adapter(Implementing the Adapter)

FastAdapter使用上面你的定義的模型類(model class)來創(chuàng)建RecyclerView.AdapterRecyclerView.ViewHolder叫惊。所以讓你的類實現(xiàn)AbstractItem<Item, Item.ViewHolder>這個接口:

  1. getType() – 返回一個唯一的ID。這里這個ID一定要是唯一的不能重復做修,推薦的使用方法是在app/res/values/目錄下新建一個文件霍狰,在其中指定。參考Android文檔說明饰及。
  2. getLayoutRes() – 返回你布局文件的id蔗坯,(R.layout.yours)
  3. bindView() – 和RecyclerView的 onBindViewHolder() 方法一樣
public class Mango extends AbstractItem<Mango, Mango.ViewHolder> {
    private String name, imageUrl, description;

    public Mango() { }

    public Mango(String name, String imageUrl) {
        this.name = name;
        this.imageUrl = imageUrl;
    }

     // Your Getter Setters here
     
    // Fast Adapter methods
    @Override
    public int getType() { return R.id.item_card; }

    @Override
    public int getLayoutRes() { return R.layout.list_item; }

    @Override
    public void bindView(ViewHolder holder) {
        super.bindView(holder);
    }

    // Manually create the ViewHolder class
    protected static class ViewHolder extends RecyclerView.ViewHolder {

        public ViewHolder(View itemView) {
            super(itemView);
        }

    }
}

綁定FastAdapter到RecyclerView(Marrying FastAdapter to RecyclerView)

FastItemAdapter<Mango> fastAdapter = new FastItemAdapter<>();
recyclerView.setAdapter(fastAdapter);
// Fetch your data here
List<Mango> mangoes = loadMangoes();
fastAdapter.add(mangoes);

就是這么簡單了,這里所做的和普通的RecyclerView.Adapter沒有什么不一樣燎含。

來回顧一下我們以前所做的事情宾濒,我們沒有做一下這些

  • 建立一個RecyclerView.Adapter的子類
  • 加載每一項的布局文件
  • 還有煩人的getItemCount()

由此可見FastAdapter確實能夠很方便的建立一個列表。但這還不是全部屏箍,F(xiàn)astAdapter還能提供更多的功能绘梦。


功能列表(Feature List)

Let’s see how some popular RecyclerView features are done with FastAdapter. Use the below index to navigate.

  1. 點擊事件 Click listener
  2. 數(shù)據(jù)過濾 Filtering data with Search
  3. 拖拽功能 Drag and drop
  4. 多項選擇 Multi-select with CAB (Contextual Action Bar) and Undo action
  5. 添加列表頭 Adding Header view (multiple ViewHolders)
  6. 無限滾動 Infinite (endless) scrolling

1. 點擊事件 Click listener <a name="Click"/>

FastAdapter設置為可選擇模式后設置點擊監(jiān)聽

fastAdapter.withSelectable(true);

fastAdapter.withOnClickListener(new FastAdapter.OnClickListener<Mango>() {
            @Override
            public boolean onClick(View v, IAdapter<Mango> adapter, Mango item, int position) {
               // Handle click here
                return true;
            }
        });

2. 數(shù)據(jù)過濾 Filtering data with Search <a name="Filtering"/>

如果你使用了SearchView橘忱,F(xiàn)astAdapter提供了接口可以為你過濾查詢結(jié)果

// Call this in your Search listener
fastAdapter.filter("yourSearchTerm");

fastAdapter.withFilterPredicate(new IItemAdapter.Predicate<Mango>() {
            @Override
            public boolean filter(Mango item, CharSequence constraint) {
                return item.getName().startsWith(String.valueOf(constraint));
            }
});

留意filter(Mango item, CharSequence constraint)方法。返回true意味著你要把這些項目從adapter中移除卸奉;如果要保留這些項目在adapter中钝诚,移除其他東西,你需要返回false榄棵。

3. 拖拽功能 Drag and drop <a name="Dragdrop"/>

首先建立一個SimpleDragCallback的實例凝颇,其次用這個實例初始化ItemTouchHelper,最后把ItemTouchHelper綁定到RecyclerView

SimpleDragCallback dragCallback = new SimpleDragCallback(this);
ItemTouchHelper touchHelper = new ItemTouchHelper(dragCallback);
touchHelper.attachToRecyclerView(recyclerView);

在Activity中實現(xiàn)ItemTouchCallback接口疹鳄,然后覆蓋itemTouchOnMove()方法

@Override
   public boolean itemTouchOnMove(int oldPosition, int newPosition) {
       Collections.swap(fastAdapter.getAdapterItems(), oldPosition, newPosition); // change position
       fastAdapter.notifyAdapterItemMoved(oldPosition, newPosition);
       return true;
   }

4. 多項選擇 Multi-select with CAB (Contextual Action Bar) and Undo action <a name="Multiselect"/>

Activity中建立一個內(nèi)部類ActionBarCallBack拧略,然后讓其實現(xiàn)ActionMode.Callback接口。

private class ActionBarCallBack implements ActionMode.Callback {
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) { return true; }
        
        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; }
        
        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            mode.finish();
            return true;
        }
        
        @Override
        public void onDestroyActionMode(ActionMode mode) { }
    }

通過FastAdapter初始化ActionModeHelper尚辑。

fastAdapter.setHasStableIds(true);
fastAdapter.withSelectable(true);
fastAdapter.withMultiSelect(true);
fastAdapter.withSelectOnLongClick(true);
actionModeHelper = new ActionModeHelper(fastAdapter, R.menu.cab, new ActionBarCallBack());

常用的onClick方法用來處理其他事件了辑鲤,比如說點擊進入細節(jié)展示頁面(detail navigation)。所以FastAdapter提供了兩個新的接口preClickpreLongClick來處理當前的選擇點擊事件(CAB)杠茬。

fastAdapter.withOnPreClickListener(new FastAdapter.OnClickListener<Mango>() {
            @Override
            public boolean onClick(View v, IAdapter<Mango> adapter, Mango item, int position) {
                Boolean res = actionModeHelper.onClick(item);
                return res != null ? res : false;
            }
});

fastAdapter.withOnPreLongClickListener(new FastAdapter.OnLongClickListener<Mango>() {
            @Override
            public boolean onLongClick(View v, IAdapter<Mango> adapter, Mango item, int position) {
                ActionMode actionMode = actionModeHelper.onLongClick(MainActivity.this, position);
                if (actionMode != null) {
                    // Set CAB background color
                   findViewById(R.id.action_mode_bar).setBackgroundColor(Color.GRAY);
                }
                return actionMode != null;
            }
});

注意月褥,如果Action Mode沒有開啟,不要處理preClick事件瓢喉,直接返回false宁赤。同時它允許我們處理常規(guī)的點擊事件。只是注意當你同時使用withOnClickListener()的時候栓票,也要返回false决左。

最后,在values/styles.xml文件中的AppTheme里開啟windowActionModeOverlay選項:

<item name="windowActionModeOverlay">true</item>

撤銷功能(Undo Action)

CAB模式中刪除項目走贪,請結(jié)合UndoHelper一起使用佛猛。這個類可以幫助我們實現(xiàn)刪除撤銷功能。

UndoHelper undoHelper = new UndoHelper(fastAdapter, new UndoHelper.UndoListener<Mango>() {
            @Override
            public void commitRemove(Set<Integer> positions, ArrayList<FastAdapter.RelativeInfo<Mango>> removed) {
                Log.d("remove", "removed: " + removed.size());
            }
        });

在這個接口的實現(xiàn)方法中我們記錄了刪除對象的個數(shù)坠狡。UndoHelper.UndoListener同時也能幫助我們得到被刪除對象在列表中的位置继找。

還記得我們創(chuàng)建的那個內(nèi)部類ActionBarCallBack嗎?我們現(xiàn)在來修改下其中的onActionItemClicked()方法:

@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    undoHelper.remove(findViewById(android.R.id.content), "Item removed", "Undo", Snackbar.LENGTH_LONG, fastAdapter.getSelections());
    mode.finish();
    return true;
}

UndoHelperremove()方法逃沿,才是最終執(zhí)行刪除對象的地方婴渡。刪除執(zhí)行以后會出現(xiàn)一個SnackBar提醒我們刪除成功,同時也給我了我們一個便捷的撤銷最后一次刪除操作的接口凯亮。

5. 添加列表頭 Adding Header view (multiple ViewHolders) <a name="Header"/>

你可以選擇修改之前創(chuàng)建的Mango類边臼,但是基于代碼整潔的考慮,我們這里新建立一個類去加載Header view假消。

public class Header extends AbstractItem<Header, Header.ViewHolder> {
    String title;
    
    public Header(String title) {
        this.title = title;
    }
    
     // Your getter setters here
     
    // AbstractItem methods
    @Override
    public int getType() {
        return R.id.header_text;
    }
    
    @Override
    public int getLayoutRes() {
        return R.layout.header_item;
    }
    
    @Override
    public void bindView(ViewHolder holder) {
        super.bindView(holder);
        holder.textView.setText(title);
    }
    
    protected static class ViewHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.header_text) TextView textView;
        public ViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }
}

這里的Header只顯示一個簡的單TextView柠并。然后開始實例化Adapter變量:

FastItemAdapter fastAdapter = new FastItemAdapter<>();
fastAdapter.setHasStableIds(true);
fastAdapter.withSelectable(true);
HeaderAdapter<Header> headerAdapter = new HeaderAdapter<>();

回憶一下之前我們是如何實例化FastAdapter的:

FastItemAdapter<Mango> fastAdapter = new FastItemAdapter<>();

但是現(xiàn)在我們并沒有直接指定類型,這樣可以使用們的FastAdapter同時保存Mango和Header兩種類型。

當然你也可以選擇實例化一個泛型FastAdapter(generic type FastAdapter):

FastItemAdapter<IItem> fastAdapter = new FastItemAdapter<>();

IItem是你自定義的Model的基類臼予,所以你可以這樣初始化Adapter亿傅,不過要記得在添加數(shù)據(jù)的時候進行數(shù)據(jù)轉(zhuǎn)換。

組裝adapter(Setting the adapter)

這里和之前有點不一樣瘟栖,我們使用wrap(FastAdapter)方法,同時把headerAdapterfastAdapter添加到RecyclerView里谅阿。

recyclerView.setAdapter(headerAdapter.wrap(fastAdapter));

如果你還想添加第三個不同類型的ViewHolder半哟,同樣可以再使用wrap()方法進行。

recyclerView.setAdapter(thirdAdapter.wrap(headerAdapter.wrap(fastAdapter)));

添加數(shù)據(jù) Adding data

我想在頂部和中間各添加一個Header view签餐,而每個Header view都應該有一個不同的數(shù)據(jù)集寓涨。比如說我想添加這樣的數(shù)據(jù):

int half = mangoes.size() / 2;

fastAdapter.add(0, new Header("First half").withIdentifier(1));
fastAdapter.add(1, mangoes.subList(0, half));
fastAdapter.add(half + 1, new Header("Second half").withIdentifier(2));
fastAdapter.add(half + 2, mangoes.subList(0, half));

在上面的代碼中,我把mangoes的前半段數(shù)據(jù)放在了第一個header后面氯檐,后半段數(shù)據(jù)放在了第二個header后面戒良。

請注意half這個int類型的變量是不斷增加的,跟其他的Listposition一樣冠摄。

使用不同的ViewHolders (Manipulating with different ViewHolders)

現(xiàn)在FastAdapter已經(jīng)包含了幾個不同View和數(shù)據(jù)糯崎,但是我們?nèi)绾问褂盟鼈兡兀勘热缣幚睃c擊河泳,還有上面介紹的其他酷炫的功能呢沃呢?

簡單來說,就是使用 Java的關(guān)鍵字:instanceof.

下面我將介紹一下如何處理FastAdapter的點擊事件:

fastAdapter.withOnClickListener(new FastAdapter.OnClickListener<IItem>() {
            @Override
            public boolean onClick(View v, IAdapter<IItem> adapter, IItem item, int position) {
                if (item instanceof Mango) {
                   // Do your thing!
                } else if (item instanceof Header) {
                   // Header clicks usually don't do anything. Ignore if you like.
                }
                return true;
            }
});

看到區(qū)別了嗎拆挥?之前我們使用同樣的監(jiān)聽點擊代碼的時候薄霜,onClick()方法傳遞的參數(shù)是Mango類型,但是當我們使用了泛型的FastAdapter時纸兔,方法的參數(shù)變成了IItem (基類) 對象惰瓜。

6. 無限滾動 Infinite (endless) scrolling <a name="Infinitescrolling"/>

這個功能主要使用在社交類型的app中,每次需要加載指定數(shù)量的信息汉矿,當你到達了信息底部的時候崎坊,出現(xiàn)一個加載標志,然后再加載指定數(shù)量的信息负甸。

實現(xiàn)這個功能的關(guān)鍵就在RecyclerViewaddOnScrollListener()方法流强。但是我們知道,真正困難的地方就在于構(gòu)建這個監(jiān)聽器呻待,基于此打月,F(xiàn)astAdapter為我們提供了EndlessRecyclerOnScrollListener()方法。

我們先寫一個FooterAdapter蚕捉,我們用這個在列表底部顯示加載數(shù)據(jù)時候的ProgressBar奏篙。

FooterAdapter<ProgressItem> footerAdapter = new FooterAdapter<>();

ProgressItemFastAdapter extensions庫中,并不是core庫中的類型,所以需要在工程中加載此庫秘通,前面有說明为严。

recyclerView.addOnScrollListener(new EndlessRecyclerOnScrollListener() {
            @Override
            public void onLoadMore(int currentPage) {
                footerAdapter.clear();
                footerAdapter.add(new ProgressItem().withEnabled(false));
                // Load your items here and add it to FastAdapter
                fastAdapter.add(newMangoes);
            }
});

實現(xiàn)無限滾動就是這樣簡單。So easy肺稀!


寫在最后 (Wrapping it up)

我想這是我最長的一篇文章了第股!為你們的耐心和堅持鼓掌。

RecyclerView的魔力就在于它的Adapter话原。FastAdapter也著力于此夕吻,盡量簡化Adapter的使用,同時致力于提供更多的功能繁仁,而不僅僅是為了方便使用涉馅。如果你需要處理大量Adapter,那么FastAdapter將是不二之選黄虱!

Resources

Source Code

文章中的代碼可以在我的GitHub Gist找到稚矿。

FastAdapter可以讓你輕快地做到普通的RecyclerView能做到的任何事。我已經(jīng)開始在我的工程中使用它了捻浦,你準備好了嗎晤揣?在下面的評論中留下你的看法吧。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末朱灿,一起剝皮案震驚了整個濱河市碉渡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌母剥,老刑警劉巖滞诺,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異环疼,居然都是意外死亡习霹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門炫隶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來淋叶,“玉大人,你說我怎么就攤上這事伪阶∩烽荩” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵栅贴,是天一觀的道長斟湃。 經(jīng)常有香客問我,道長檐薯,這世上最難降的妖魔是什么凝赛? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任注暗,我火速辦了婚禮,結(jié)果婚禮上墓猎,老公的妹妹穿的比我還像新娘捆昏。我一直安慰自己,他們只是感情好毙沾,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布骗卜。 她就那樣靜靜地躺著,像睡著了一般左胞。 火紅的嫁衣襯著肌膚如雪膨俐。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天罩句,我揣著相機與錄音,去河邊找鬼敛摘。 笑死门烂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的兄淫。 我是一名探鬼主播屯远,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼捕虽!你這毒婦竟也來了慨丐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤泄私,失蹤者是張志新(化名)和其女友劉穎房揭,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體晌端,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡捅暴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了咧纠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蓬痒。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖漆羔,靈堂內(nèi)的尸體忽然破棺而出梧奢,到底是詐尸還是另有隱情,我是刑警寧澤演痒,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布亲轨,位于F島的核電站,受9級特大地震影響鸟顺,放射性物質(zhì)發(fā)生泄漏瓶埋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望养筒。 院中可真熱鬧曾撤,春花似錦、人聲如沸晕粪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽巫湘。三九已至装悲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間尚氛,已是汗流浹背诀诊。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留阅嘶,地道東北人属瓣。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像讯柔,于是被迫代替她去往敵國和親抡蛙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

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