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能做到的任何事。我已经开始在我的工程中使用它了,你准备好了吗?在下面的评论中留下你的看法吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容