官方網站: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. 只配置了dependencies
2. 配置repositories,但是位置錯了,build.gradle(Project:XXXX) 文件下的repositories有兩個,一個是buildscript下面的,一個是allprojects下面的,要配置到allprojects下面纔是對的。
3. 版本號前面多一個v,這個是我的鍋,在2.1.2版本之前都是帶v的,之後(包含2.1.2)都不需要帶v。 - 網絡原因(這個就不解釋了)
使用Adapter
和原始的adapter相對,減少70%的代碼量。
使用代碼
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不做處理都會出現問題,這個本質上是由於佈局重用機制導致的,解決辦法是通過數據狀態來控制控件的狀態,一定要設置狀態無論什麼狀態,if
和else
是少不了的,如下代碼:
if(entity.isCheck){
checkBox.isChecked(true);
} else {
checkBox.isChecked(false);
}
解決緩存問題案例:
添加Item事件
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。
添加列表加載動畫
開啓動畫(默認爲漸顯效果)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);
}
添加頭部、尾部
添加
mQuickAdapter.addHeaderView(getView());
mQuickAdapter.addFooterView(getView());
注:如果想深入瞭解的原理可以查看BaseRecyclerAdapter之添加不同佈局(頭部尾部)
刪除指定view
mQuickAdapter.removeHeaderView(getView);
mQuickAdapter.removeFooterView(getView);
刪除所有
mQuickAdapter.removeAllHeaderView();
mQuickAdapter.removeAllFooterView();
默認出現了頭部就不會顯示Empty,和尾部,配置以下方法也支持同時顯示:
setHeaderAndEmpty
setHeaderFooterEmpty
默認頭部尾部都是佔滿一行,如果需要不佔滿可以配置:
setHeaderViewAsFlow
setFooterViewAsFlow
自動加載
上拉加載
// 滑動最後一個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>
下拉加載(符合聊天軟件下拉歷史數據需求)
設置開啓開關
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();
}
});
}
多佈局
實體類必須實現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()兩個參數寫反了。
設置空佈局
// 沒有數據的時候默認顯示該佈局
mQuickAdapter.setEmptyView(getView());
如果用網格佈局的話,設置空佈局就不能給全屏,可以使用瀑布流佈局。
添加拖拽、滑動刪除
拖拽和滑動刪除的回調方法
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
即可
樹形列表
例子:三級菜單
// 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。
混淆
-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。
- PinnedSectionItemDecoration:一個強大的粘性標籤庫
- EasyRefreshLayout:這個庫讓你輕鬆實現下拉刷新和上拉更多
- EasySwipeMenuLayout:獨立的側滑刪除
本文章由於持續更新,建議點贊收藏,便於查看。
官方網站:www.recyclerview.org
Demo下載地址:http://fir.im/s91g
如果有問題:提問,請先看這個!