BRVAH官方使用指南(持續更新)

972352-9911637db5512613.png
www.recyclerview.org

官方網站:www.recyclerview.org

BRVAH是一個強大的RecyclerAdapter框架(什麼是RecyclerView?),它能節約開發者大量的開發時間,集成了大部分列表常用需求解決方案。爲什麼會有它?請查看「Android開源框架BRVAH由來篇」該框架於2016年4月10號發佈的第1個版本到現在已經一年多了,經歷了800多次代碼提交,140多次版本打包,修復了1000多個問題,獲得了9000多star,非常感謝大家的使用以及反饋。
本篇爲BRVAH的使用指南以及包含常見問題會第一時間更新最新的使用方法。最新版本請查看releases,由於持續更新。

文章目錄

  • 框架引入
  • 優化Adapter代碼
    和原始的adapter相對,減少70%的代碼量。
  • 添加Item事件
    Item的點擊事件
    Item的長按事件
    Item子控件的點擊事件
    Item子控件的長按事件
  • 添加列表加載動畫
    一行代碼輕鬆切換5種默認動畫。
  • 添加頭部、尾部
    一行代碼搞定,感覺又回到ListView時代。
  • 自動加載
    上拉加載無需監聽滑動事件,可自定義加載佈局,顯示異常提示,自定義異常提示。同時支持下拉加載。
  • 分組佈局
    隨心定義分組頭部。
  • 多佈局
    簡單配置、無需重寫額外方法。
  • 設置空佈局
    比Listview的setEmptyView還要好用。
  • 添加拖拽、滑動刪除
    開啓,監聽即可,就是這麼簡單。
  • 樹形列表
    比ExpandableListView還要強大,支持多級。
  • 自定義ViewHolder
    支持自定義ViewHolder,讓開發者隨心所欲。
  • 擴展框架
    組合第三方框架,輕鬆實現更多需求定製。

框架引入

先在 build.gradle(Project:XXXX) 的 repositories 添加:

    allprojects {
        repositories {
            ...
            maven { url "https://jitpack.io" }
        }
    }

然後在 build.gradle(Module:app) 的 dependencies 添加:

    dependencies {
            compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.30'
    }

更新說明:https://github.com/CymChad/BaseRecyclerViewAdapterHelper/releases

注意

版本:2.9.28
Change the method setVisible --> setGone
The new method setVisible

setVisible:Set a view visibility to VISIBLE (true) or INVISIBLE (false).
setGone: Set a view visibility to VISIBLE (true) or GONE (false).

注意: 一旦出現加載失敗的情況,只有兩種情況:

  1. 配置沒配置好
    配置沒配置好,有幾種情況:
    1. 只配置了dependencies
    2. 配置repositories,但是位置錯了,build.gradle(Project:XXXX) 文件下的repositories有兩個,一個是buildscript下面的,一個是allprojects下面的,要配置到allprojects下面纔是對的。
    3. 版本號前面多一個v,這個是我的鍋,在2.1.2版本之前都是帶v的,之後(包含2.1.2)都不需要帶v。
  2. 網絡原因(這個就不解釋了)

使用Adapter

和原始的adapter相對,減少70%的代碼量。

972352-bbacc52dd247f621.gif

使用代碼

public class HomeAdapter extends BaseQuickAdapter<HomeItem, BaseViewHolder> {
    public HomeAdapter(int layoutResId, List data) {
        super(layoutResId, data);
    }

    @Override
    protected void convert(BaseViewHolder helper, HomeItem item) {
        helper.setText(R.id.text, item.getTitle());
        helper.setImageResource(R.id.icon, item.getImageResource());
        // 加載網絡圖片
      Glide.with(mContext).load(item.getUserAvatar()).crossFade().into((ImageView) helper.getView(R.id.iv));
    }
}

注:如果想深入瞭解的原理可以查看RecyclerView.Adapter優化了嗎?

使用

首先需要繼承BaseQuickAdapter,然後BaseQuickAdapter<Status, BaseViewHolder>第一個泛型Status是數據實體類型,第二個BaseViewHolder是ViewHolder其目的是爲了支持擴展ViewHolder。

賦值

可以直接使用viewHolder對象點相關方法通過傳入viewId和數據進行,方法支持鏈式調用。如果是加載網絡圖片或自定義view可以通過viewHolder.getView(viewId)獲取該控件。

常用方法

  • viewHolder.getLayoutPosition() 獲取當前item的position

常見問題

這些問題不是使用該庫的問題,但是經常有人問這些問題,所以特意寫出來,幫助後續遇到以下問題的開發者們。

爲什麼有數據不顯示?
請檢查一下你的RecyclerView是否設置了LayoutManager。

爲什麼有10條數據,只顯示1條?
請檢查一下item的佈局最外層的Layout是不是layout_height設置了match_parent.

數據狀態錯亂
這個問題無論是RecyclerView還是ListView不做處理都會出現問題,這個本質上是由於佈局重用機制導致的,解決辦法是通過數據狀態來控制控件的狀態,一定要設置狀態無論什麼狀態,ifelse是少不了的,如下代碼:

  if(entity.isCheck){
    checkBox.isChecked(true);
  } else {
    checkBox.isChecked(false);
  }

解決緩存問題案例:

添加Item事件

972352-2782db0d3f5a85d6.gif

Item的點擊事件

adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
            @Override
            public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
                Log.d(TAG, "onItemClick: ");
                Toast.makeText(ItemClickActivity.this, "onItemClick" + position, Toast.LENGTH_SHORT).show();
            }
        });

Item的長按事件

adapter.setOnItemLongClickListener(new BaseQuickAdapter.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position) {
                Log.d(TAG, "onItemLongClick: ");
                Toast.makeText(ItemClickActivity.this, "onItemLongClick" + position, Toast.LENGTH_SHORT).show();
                return false;
            }
        });

注意:嵌套recycleView的情況下需要使用你使用 adapter. setOnItemClickListener 來設置點擊事件,如果使用recycleView.addOnItemTouchListener會累計添加的。

Item子控件的點擊事件
首先在adapter的convert方法裏面通過viewHolder.addOnClickListener綁定一下的控件id

 @Override
    protected void convert(BaseViewHolder viewHolder, Status item) {
        viewHolder.setText(R.id.tweetName, item.getUserName())
                .setText(R.id.tweetText, item.getText())
                .setText(R.id.tweetDate, item.getCreatedAt())
                .setVisible(R.id.tweetRT, item.isRetweet())
                .addOnClickListener(R.id.tweetAvatar)
                .addOnClickListener(R.id.tweetName)
                .linkify(R.id.tweetText);
       
    }

然後在設置

 adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
            @Override
            public boolean onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
                Log.d(TAG, "onItemChildClick: ");
                Toast.makeText(ItemClickActivity.this, "onItemChildClick" + position, Toast.LENGTH_SHORT).show();
                return false;
            }
        });

Item子控件的長按事件
步驟同上使用方法不同。
adapter中綁定方法將addOnClickListener改成addOnLongClickListener.
設置點擊事件方法setOnItemChildClickListener改成setOnItemChildLongClickListener

注意:設置子控件的事件,如果不在adapter中綁定,點擊事件無法生效,因爲無法找到你需要設置的控件。

如果需要在點擊事件中獲取其他子控件可以使用:

getViewByPosition(RecyclerView recyclerView, int position, @IdRes int viewId) 

注意:如果有header的話需要處理一下position加上 headerlayoutcount。

添加列表加載動畫

972352-fda353fc3d9f67d7.gif

開啓動畫(默認爲漸顯效果)
adapter.openLoadAnimation();
默認提供5種方法(漸顯、縮放、從下到上,從左到右、從右到左)

public static final int ALPHAIN = 0x00000001;
    /**
     * Use with {@link #openLoadAnimation}
     */
    public static final int SCALEIN = 0x00000002;
    /**
     * Use with {@link #openLoadAnimation}
     */
    public static final int SLIDEIN_BOTTOM = 0x00000003;
    /**
     * Use with {@link #openLoadAnimation}
     */
    public static final int SLIDEIN_LEFT = 0x00000004;
    /**
     * Use with {@link #openLoadAnimation}
     */
    public static final int SLIDEIN_RIGHT = 0x00000005;

切換動畫

quickAdapter.openLoadAnimation(BaseQuickAdapter.ALPHAIN);

自定義動畫

quickAdapter.openLoadAnimation(new BaseAnimation() {
                            @Override
                            public Animator[] getAnimators(View view) {
                                return new Animator[]{
                                        ObjectAnimator.ofFloat(view, "scaleY", 1, 1.1f, 1),
                                        ObjectAnimator.ofFloat(view, "scaleX", 1, 1.1f, 1)
                                };
                            }
                        });

動畫默認只執行一次,如果想重複執行可設置

mQuickAdapter.isFirstOnly(false);

注:如果想深入瞭解的原理可以查看BaseRecyclerAdapter之添加動畫(策略模式)

設置不顯示動畫數量

adapter.setNotDoAnimationCount(count);

首次到界面的item都依次執行加載動畫

由於進入界面的item都是很多的速度進來的所以不會出現滑動顯示的依次執行動畫效果,這個時候會一起執行動畫,如果覺得這樣的效果不好可以使用setNotDoAnimationCount設置第一屏item不執行動畫,但是如果需要依次執行動畫可以重寫startAnim讓第一個屏幕的item動畫延遲執行即可。

@Override
    protected void startAnim(Animator anim, int index) {
        super.startAnim(anim, index);
        if (index < count)
        anim.setStartDelay(index * 150);
    }

添加頭部、尾部

972352-ef9eb5bc94c8b165.gif

添加

mQuickAdapter.addHeaderView(getView());
mQuickAdapter.addFooterView(getView());

注:如果想深入瞭解的原理可以查看BaseRecyclerAdapter之添加不同佈局(頭部尾部)

刪除指定view

mQuickAdapter.removeHeaderView(getView);
mQuickAdapter.removeFooterView(getView);

刪除所有

mQuickAdapter.removeAllHeaderView();
mQuickAdapter.removeAllFooterView();

默認出現了頭部就不會顯示Empty,和尾部,配置以下方法也支持同時顯示:

setHeaderAndEmpty
setHeaderFooterEmpty

默認頭部尾部都是佔滿一行,如果需要不佔滿可以配置:

setHeaderViewAsFlow
setFooterViewAsFlow

自動加載

上拉加載


972352-a06197a4e0aca2b7.gif
// 滑動最後一個Item的時候回調onLoadMoreRequested方法
setOnLoadMoreListener(RequestLoadMoreListener);

默認第一次加載會進入回調,如果不需要可以配置:

mQuickAdapter.disableLoadMoreIfNotFullPage();

回調處理代碼

mQuickAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
            @Override public void onLoadMoreRequested() {
                mRecyclerView.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (mCurrentCounter >= TOTAL_COUNTER) {
                            //數據全部加載完畢
                            mQuickAdapter.loadMoreEnd();
                        } else {
                            if (isErr) {
                                //成功獲取更多數據
                                mQuickAdapter.addData(DataServer.getSampleData(PAGE_SIZE));
                                mCurrentCounter = mQuickAdapter.getData().size();
                                mQuickAdapter.loadMoreComplete();
                            } else {
                                //獲取更多數據失敗
                                isErr = true;
                                Toast.makeText(PullToRefreshUseActivity.this, R.string.network_err, Toast.LENGTH_LONG).show();
                                mQuickAdapter.loadMoreFail();

                            }
                        }
                    }

                }, delayMillis);
            }
        }, mReyclerView);

加載完成(注意不是加載結束,而是本次數據加載結束並且還有下頁數據)

mQuickAdapter.loadMoreComplete();

加載失敗

mQuickAdapter.loadMoreFail();

加載結束

mQuickAdapter.loadMoreEnd();

注意:如果上拉結束後,下拉刷新需要再次開啓上拉監聽,需要使用setNewData方法填充數據。

打開或關閉加載(一般用於下拉的時候做處理,因爲上拉下拉不能同時操作)

mQuickAdapter.setEnableLoadMore(boolean);

預加載

// 當列表滑動到倒數第N個Item的時候(默認是1)回調onLoadMoreRequested方法
mQuickAdapter.setPreLoadNumber(int);

設置自定義加載佈局

mQuickAdapter.setLoadMoreView(new CustomLoadMoreView());
public final class CustomLoadMoreView extends LoadMoreView {

    @Override public int getLayoutId() {
        return R.layout.view_load_more;
    }

    /**
     * 如果返回true,數據全部加載完畢後會隱藏加載更多
     * 如果返回false,數據全部加載完畢後會顯示getLoadEndViewId()佈局
     */
    @Override public boolean isLoadEndGone() {
        return true;
    }

    @Override protected int getLoadingViewId() {
        return R.id.load_more_loading_view;
    }

    @Override protected int getLoadFailViewId() {
        return R.id.load_more_load_fail_view;
    }

    /**
     * isLoadEndGone()爲true,可以返回0
     * isLoadEndGone()爲false,不能返回0
     */
    @Override protected int getLoadEndViewId() {
        return 0;
    }
}
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="@dimen/dp_40">

    <LinearLayout
        android:id="@+id/load_more_loading_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/loading_progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_marginRight="@dimen/dp_4"
            android:indeterminateDrawable="@drawable/sample_footer_loading_progress"/>

        <TextView
            android:id="@+id/loading_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="@dimen/dp_4"
            android:text="@string/loading"
            android:textColor="#0dddb8"
            android:textSize="@dimen/sp_14"/>
    </LinearLayout>

    <FrameLayout
        android:id="@+id/load_more_load_fail_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone">


        <TextView
            android:id="@+id/tv_prompt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:textColor="#0dddb8"
            android:text="@string/load_failed"/>

    </FrameLayout>

</FrameLayout>

下拉加載(符合聊天軟件下拉歷史數據需求)


972352-94bb55810befcb5b.gif

設置開啓開關

 mAdapter.setUpFetchEnable(true);

設置監聽

mAdapter.setUpFetchListener(new BaseQuickAdapter.UpFetchListener() {
            @Override
            public void onUpFetch() {
                startUpFetch();
            }
        });

private void startUpFetch() {
        count++;
        /**
         * set fetching on when start network request.
         */
        mAdapter.setUpFetching(true);
        /**
         * get data from internet.
         */
        mRecyclerView.postDelayed(new Runnable() {
            @Override
            public void run() {
                mAdapter.addData(0, genData());
                /**
                 * set fetching off when network request ends.
                 */
                mAdapter.setUpFetching(false);
                /**
                 * set fetch enable false when you don't need anymore.
                 */
                if (count > 5) {
                    mAdapter.setUpFetchEnable(false);
                }
            }
        }, 300);
    }

開始加載的位置

mAdapter.setStartUpFetchPosition(2);

分組佈局

實體類必須繼承SectionEntity

public class MySection extends SectionEntity<Video> {
    private boolean isMore;
    public MySection(boolean isHeader, String header) {
        super(isHeader, header);
    }

    public MySection(Video t) {
        super(t);
    }
}

adapter構造需要傳入兩個佈局id,第一個是item的,第二個是head的,在convert方法裏面加載item數據,在convertHead方法裏面加載head數據

public class SectionAdapter extends BaseSectionQuickAdapter<MySection> {
     public SectionAdapter(int layoutResId, int sectionHeadResId, List data) {
        super(layoutResId, sectionHeadResId, data);
    }
    @Override
    protected void convert(BaseViewHolder helper, MySection item) {
        helper.setImageUrl(R.id.iv, (String) item.t);
    }
    @Override
    protected void convertHead(BaseViewHolder helper,final MySection item) {
        helper.setText(R.id.header, item.header);
       
        helper.setOnClickListener(R.id.more, new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(context,item.header+"more..",Toast.LENGTH_LONG).show();
            }
        });
    }

多佈局

972352-aa122442f6568954.gif

實體類必須實現MultiItemEntity,在設置數據的時候,需要給每一個數據設置itemType

public class MultipleItem implements MultiItemEntity {
    public static final int TEXT = 1;
    public static final int IMG = 2;
    private int itemType;

    public MultipleItem(int itemType) {
        this.itemType = itemType;
    }

    @Override
    public int getItemType() {
        return itemType;
    }
}

在構造裏面addItemType綁定type和layout的關係

public class MultipleItemQuickAdapter extends BaseMultiItemQuickAdapter<MultipleItem, BaseViewHolder> {

    public MultipleItemQuickAdapter(List data) {
        super(data);
        addItemType(MultipleItem.TEXT, R.layout.text_view);
        addItemType(MultipleItem.IMG, R.layout.image_view);
    }

    @Override
    protected void convert(BaseViewHolder helper, MultipleItem item) {
        switch (helper.getItemViewType()) {
            case MultipleItem.TEXT:
                helper.setImageUrl(R.id.tv, item.getContent());
                break;
            case MultipleItem.IMG:
                helper.setImageUrl(R.id.iv, item.getContent());
                break;
        }
    }

}

注:如果想深入瞭解的原理可以查看BaseRecyclerAdapter之添加不同佈局(優化篇)

如果考慮到在GridLayoutManager複用item問題可以配置:

  multipleItemAdapter.setSpanSizeLookup(new BaseQuickAdapter.SpanSizeLookup() {
            @Override
            public int getSpanSize(GridLayoutManager gridLayoutManager, int position) {
                return data.get(position).getSpanSize();
            }
        });

如果大家覺得這種多佈局方式有點由於耦合了實體類,還有支持另外一種多佈局方式具體可查看更便捷的多佈局, 爲 BaseQuickAdapter 設置代理.

如果使用多佈局出現這個NotFoundException異常,有可能是addItemType()兩個參數寫反了

設置空佈局

972352-0f4c2ff4ba11865f.gif
// 沒有數據的時候默認顯示該佈局
mQuickAdapter.setEmptyView(getView());

如果用網格佈局的話,設置空佈局就不能給全屏,可以使用瀑布流佈局。

添加拖拽、滑動刪除

972352-0280a406c0196d1a.gif

拖拽和滑動刪除的回調方法

OnItemDragListener onItemDragListener = new OnItemDragListener() {
    @Override
    public void onItemDragStart(RecyclerView.ViewHolder viewHolder, int pos){}
    @Override
    public void onItemDragMoving(RecyclerView.ViewHolder source, int from, RecyclerView.ViewHolder target, int to) {}
    @Override
    public void onItemDragEnd(RecyclerView.ViewHolder viewHolder, int pos) {}
}

OnItemSwipeListener onItemSwipeListener = new OnItemSwipeListener() {
    @Override
    public void onItemSwipeStart(RecyclerView.ViewHolder viewHolder, int pos) {}
    @Override
    public void clearView(RecyclerView.ViewHolder viewHolder, int pos) {}
    @Override
    public void onItemSwiped(RecyclerView.ViewHolder viewHolder, int pos) {}
};

adapter需要繼承BaseItemDraggableAdapter

public class ItemDragAdapter extends BaseItemDraggableAdapter<String, BaseViewHolder> {
    public ItemDragAdapter(List data) {
        super(R.layout.item_draggable_view, data);
    }

    @Override
    protected void convert(BaseViewHolder helper, String item) {
        helper.setText(R.id.tv, item);
    }
}

Activity使用代碼

mAdapter = new ItemDragAdapter(mData);

ItemDragAndSwipeCallback itemDragAndSwipeCallback = new ItemDragAndSwipeCallback(mAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemDragAndSwipeCallback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);

// 開啓拖拽
mAdapter.enableDragItem(itemTouchHelper, R.id.textView, true);
mAdapter.setOnItemDragListener(onItemDragListener);

// 開啓滑動刪除
mAdapter.enableSwipeItem();
mAdapter.setOnItemSwipeListener(onItemSwipeListener);

默認不支持多個不同的 ViewType 之間進行拖拽,如果開發者有所需求:

重寫ItemDragAndSwipeCallback裏的onMove()方法,return true即可

樹形列表

972352-a6ec71ff8c26cd9e.gif

例子:三級菜單

// if you don't want to extent a class, you can also use the interface IExpandable.
// AbstractExpandableItem is just a helper class.
public class Level0Item extends AbstractExpandableItem<Level1Item> {...}
public class Level1Item extends AbstractExpandableItem<Person> {...}
public class Person {...}

adapter需要繼承BaseMultiItemQuickAdapter

public class ExpandableItemAdapter extends BaseMultiItemQuickAdapter<MultiItemEntity, BaseViewHolder> { 
    public ExpandableItemAdapter(List<MultiItemEntity> data) {    
        super(data);
        addItemType(TYPE_LEVEL_0, R.layout.item_expandable_lv0);   
        addItemType(TYPE_LEVEL_1, R.layout.item_expandable_lv1);    
        addItemType(TYPE_PERSON, R.layout.item_text_view);
    }
    @Override
    protected void convert(final BaseViewHolder holder, final MultiItemEntity item) {
        switch (holder.getItemViewType()) {
        case TYPE_LEVEL_0:
            ....
            //set view content
           holder.itemView.setOnClickListener(new View.OnClickListener() {
               @Override
               public void onClick(View v) {
                   int pos = holder.getAdapterPosition();
                   if (lv0.isExpanded()) { 
                       collapse(pos);
                   } else {
                       expand(pos);
                   }
           }});
           break;
        case TYPE_LEVEL_1:
           // similar with level 0
           break;
        case TYPE_PERSON:
           //just set the content
           break;
    }
}

開啓所有菜單:

adapter.expandAll();

刪除某一個item(添加和修改的思路是一樣的)

// 獲取當前父級位置
 int cp = getParentPosition(person);
// 通過父級位置找到當前list,刪除指定下級
 ((Level1Item)getData().get(cp)).removeSubItem(person);
// 列表層刪除相關位置的數據
 getData().remove(holder.getLayoutPosition());
// 更新視圖
 notifyDataSetChanged();

自定義ViewHolder

需要繼承BaseViewHolder

 public class MovieViewHolder extends BaseViewHolder 

然後修改adapter的第二個泛型爲自定義的ViewHolder

public class DataBindingUseAdapter extends BaseQuickAdapter<Movie, DataBindingUseAdapter.MovieViewHolder>

注意:需要單獨建一個外部類繼承BaseViewHolder,否則部分機型會出現ClassCastException,如果是內部類的構造方法要是public,定義的那個類也最好是public。

972352-12da25a995ddc92f.jpeg

混淆

-keep class com.chad.library.adapter.** {
*;
}
-keep public class * extends com.chad.library.adapter.base.BaseQuickAdapter
-keep public class * extends com.chad.library.adapter.base.BaseViewHolder
-keepclassmembers  class **$** extends com.chad.library.adapter.base.BaseViewHolder {
     <init>(...);
}

擴展框架

由於adapter本身能力有限,我們又不想耦合view層所以有些需求是現實不了,於是合作了一些優秀開源庫,爲開發者提供更多可能性。以下擴展框架都是有結合BRVAH的demo。

本文章由於持續更新,建議點贊收藏,便於查看。

官方網站:www.recyclerview.org
Demo下載地址:http://fir.im/s91g

如果有問題:提問,請先看這個!

972352-4c69b5adcde1b0d0.gif
發佈了35 篇原創文章 · 獲贊 2 · 訪問量 4382
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章