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]就更復雜了,這里面還不包括處理各種點擊事件,點擊拖動等等其他一些很酷的功能。如果你正為此發愁,那么這篇文章就是為你準備的。當然如果你熟悉[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

穩定、高效、簡單的adapter庫,能夠有效節省開發時間

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

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

那么,還等什么?上車吧,騷年!

(滴~~~,學生卡??!)

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)

用作者最喜歡的芒果為例,寫個相關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
}

實現Adapter(Implementing the Adapter)

FastAdapter使用上面你的定義的模型類(model class)來創建RecyclerView.AdapterRecyclerView.ViewHolder。所以讓你的類實現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確實能夠很方便的建立一個列表。但這還不是全部,FastAdapter還能提供更多的功能。


功能列表(Feature List)

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

  1. 點擊事件 Click listener
  2. 數據過濾 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設置為可選擇模式后設置點擊監聽

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. 數據過濾 Filtering data with Search <a name="Filtering"/>

如果你使用了SearchView,FastAdapter提供了接口可以為你過濾查詢結果

// 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中實現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中建立一個內部類ActionBarCallBack,然后讓其實現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方法用來處理其他事件了,比如說點擊進入細節展示頁面(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。同時它允許我們處理常規的點擊事件。只是注意當你同時使用withOnClickListener()的時候,也要返回false。

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

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

撤銷功能(Undo Action)

CAB模式中刪除項目,請結合UndoHelper一起使用。這個類可以幫助我們實現刪除撤銷功能。

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());
            }
        });

在這個接口的實現方法中我們記錄了刪除對象的個數。UndoHelper.UndoListener同時也能幫助我們得到被刪除對象在列表中的位置。

還記得我們創建的那個內部類ActionBarCallBack嗎?我們現在來修改下其中的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()方法,才是最終執行刪除對象的地方。刪除執行以后會出現一個SnackBar提醒我們刪除成功,同時也給我了我們一個便捷的撤銷最后一次刪除操作的接口。

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

你可以選擇修改之前創建的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<>();

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

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

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

IItem是你自定義的Model的基類,所以你可以這樣初始化Adapter,不過要記得在添加數據的時候進行數據轉換。

組裝adapter(Setting the adapter)

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

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

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

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

添加數據 Adding data

我想在頂部和中間各添加一個Header view,而每個Header view都應該有一個不同的數據集。比如說我想添加這樣的數據:

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的前半段數據放在了第一個header后面,后半段數據放在了第二個header后面。

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

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

現在FastAdapter已經包含了幾個不同View和數據,但是我們如何使用它們呢?比如處理點擊,還有上面介紹的其他酷炫的功能呢?

簡單來說,就是使用 Java的關鍵字: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;
            }
});

看到區別了嗎?之前我們使用同樣的監聽點擊代碼的時候,onClick()方法傳遞的參數是Mango類型,但是當我們使用了泛型的FastAdapter時,方法的參數變成了IItem (基類) 對象。

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

這個功能主要使用在社交類型的app中,每次需要加載指定數量的信息,當你到達了信息底部的時候,出現一個加載標志,然后再加載指定數量的信息。

實現這個功能的關鍵就在RecyclerViewaddOnScrollListener()方法。但是我們知道,真正困難的地方就在于構建這個監聽器,基于此,FastAdapter為我們提供了EndlessRecyclerOnScrollListener()方法。

我們先寫一個FooterAdapter,我們用這個在列表底部顯示加載數據時候的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);
            }
});

實現無限滾動就是這樣簡單。So easy!


寫在最后 (Wrapping it up)

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

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

Resources

Source Code

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

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容