前言
主要聲明三點(diǎn)
第一:
關(guān)于本文的BaseRecyclerViewAdapterHelper用法,自然是轉(zhuǎn)載的官方原文。或許有的人會(huì)很疑惑原文寫(xiě)的那么清除那么完善,為什么我還要寫(xiě)一篇幾乎一模一樣的?是為了蹭熱度讓更多的人關(guān)注?有更多的點(diǎn)擊量?當(dāng)然不是,我對(duì)這么虛偽的東西是非常不齒的。那么自然我有我的原因:
1、我自己其實(shí)是個(gè)小白,剛?cè)脒@行不久,由于接觸這行時(shí)間也不長(zhǎng),加上對(duì)這行腦袋瓜比較笨,所以學(xué)起來(lái)特別吃力,很多東西明明剛用過(guò)的,轉(zhuǎn)過(guò)頭又忘了怎么回事,所以也是特別痛苦;
2、正如第一條所說(shuō)的,由于太笨,所以想著記錄一些筆記來(lái)幫助日后的開(kāi)發(fā),所以.....對(duì),嚴(yán)格意義上來(lái)說(shuō)這只是我的個(gè)人學(xué)習(xí)筆記;
3、當(dāng)然,由于畢竟是在開(kāi)源的環(huán)境下,相信有的同行搜索相關(guān)文章時(shí)是會(huì)搜到我的文章,所以我寫(xiě)的還是相對(duì)比較正規(guī)的,如果只是我自己看,我是不會(huì)這樣記筆記的,以防不幸看到我的文章的童鞋看不懂而吐槽。
-
第二:
本文所有內(nèi)容都是官網(wǎng)上所有的,所以我要特別聲明:
1、本文其實(shí)只是按照自己的排版習(xí)慣重新排版而已;
2、在重寫(xiě)排版的基礎(chǔ)上,增加了一些簡(jiǎn)單的Demo,以及我在寫(xiě)Demo過(guò)程中碰上的少量的問(wèn)題,當(dāng)然這些問(wèn)題僅僅是我個(gè)人使用的時(shí)候碰上的,正如第一條所說(shuō)的,我本身也是小白,也是在學(xué)習(xí)階段,對(duì)很多東西也是理解的不夠,所以會(huì)導(dǎo)致或多或少的問(wèn)題,所以我像我其他文章一樣,我還是強(qiáng)烈建議不幸看到本文的同行去看原文學(xué)習(xí)使用:BRVAH官方使用指南(持續(xù)更新)http://www.itdecent.cn/p/b343fcff51b0
第三:
該適配器雖號(hào)稱萬(wàn)能適配器,但并非萬(wàn)能,有很多功能本身是實(shí)現(xiàn)不了的,還會(huì)和其他開(kāi)源的沖突,比如不能和XRecyclerView一起使用(至少我用的時(shí)候是真的沖突)。第四:
關(guān)于文章中所提到的 "bug",我相信都不存在,只是由于時(shí)間關(guān)系,無(wú)心再去研究,但是我也堅(jiān)信你繼續(xù)看本文,還是能夠知道該適配器的使用方式的。
一、框架引入
- 先在項(xiàng)目的 build.gradle(Project:XXXX) 的 repositories 添加:
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
- 然后在Module的 build.gradle(Module:app) 的 dependencies 添加:
dependencies {
......
compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.22'
}
注意: 一旦出現(xiàn)加載失敗的情況,只有兩種情況:
- 一是:配置沒(méi)配置好
配置沒(méi)配置好,有幾種情況:
1. 只配置了 dependencies
2. 配置 repositories,但是位置錯(cuò)了,build.gradle(Project:XXXX) 文件下的repositories有兩個(gè),一個(gè)是buildscript下面的,一個(gè)是allprojects下面的,要配置到allprojects下面才是對(duì)的。
3. 版本號(hào)前面多一個(gè)v,這個(gè)是我的鍋,在2.1.2版本之前都是帶v的,之后(包含2.1.2)都不需要帶v。
- 二是:網(wǎng)絡(luò)原因(這個(gè)就不解釋了)
二、Adapter的最基本使用方法
2.1 常用示例代碼
和原生的adapter相比,減少70%的代碼量。
首先看一段使用示例代碼:
public class QuickAdapter extends BaseQuickAdapter<Status, BaseViewHolder> {
public QuickAdapter() {
super(R.layout.tweet, DataServer.getSampleData());
}
@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())
.linkify(R.id.tweetText);
Glide.with(mContext).load(item.getUserAvatar()).crossFade().into((ImageView) viewHolder.getView(R.id.iv));
}
}
- 從上文中的實(shí)例代碼我們可以看出以下幾點(diǎn):
1、使用: 首先需要繼承BaseQuickAdapter,然后BaseQuickAdapter<Status, BaseViewHolder>第一個(gè)泛型Status是數(shù)據(jù)實(shí)體類型,第二個(gè)BaseViewHolder是ViewHolder其目的是為了支持?jǐn)U展ViewHolder。
2、賦值:可以直接使用viewHolder對(duì)象點(diǎn)相關(guān)方法通過(guò)傳入viewId和數(shù)據(jù)進(jìn)行,方法支持鏈?zhǔn)秸{(diào)用。如果是加載網(wǎng)絡(luò)圖片或自定義view可以通過(guò)viewHolder.getView(viewId)獲取該控件。
- 當(dāng)然這里有一個(gè)比較常用的方法:
viewHolder.getLayoutPosition() 獲取當(dāng)前item的position
2.2 最基本使用示例Demo
要是剛接觸Android的朋友要是還是不知道怎么用,那我這里提供一個(gè)很簡(jiǎn)單的demo:
-
比如說(shuō)我想要實(shí)現(xiàn)的效果大致如下:
第一步:在布局文件中引入RecyclerView
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
- 第二步:編寫(xiě)條目布局文件
item_rv.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp">
<ImageView
android:id="@+id/iv_img"
android:layout_width="150dp"
android:layout_height="80dp" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_toRightOf="@+id/iv_img"
android:text="我是標(biāo)題"
android:textColor="#f00"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_title"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_toRightOf="@id/iv_img"
android:text="我是描述" />
</RelativeLayout>
- 第三步:編寫(xiě)數(shù)據(jù)實(shí)體類型
Model.java
public class Model {
private String title;
private String content;
private String imgUrl;
//生成set、get方法
......
}
- 第四步:編寫(xiě)適配器
MyAdapter.java
public class MyAdapter extends BaseQuickAdapter<Model, BaseViewHolder> {
public MyAdapter(@LayoutRes int layoutResId, @Nullable List<Model> data) {
super(layoutResId, data);
}
@Override
protected void convert(BaseViewHolder helper, Model item) {
//可鏈?zhǔn)秸{(diào)用賦值
helper.setText(R.id.tv_title, item.getTitle())
.setText(R.id.tv_content, item.getContent())
.setImageResource(R.id.iv_img, R.mipmap.ic_launcher);
//獲取當(dāng)前條目position
//int position = helper.getLayoutPosition();
}
}
- 最后一步:在Activity中使用該適配器
MainActivity.java
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<Model> datas;
private MyAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化RecyclerView
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
//模擬的數(shù)據(jù)(實(shí)際開(kāi)發(fā)中一般是從網(wǎng)絡(luò)獲取的)
datas = new ArrayList<>();
Model model;
for (int i = 0; i < 15; i++) {
model = new Model();
model.setTitle("我是第" + i + "條標(biāo)題");
model.setContent("第" + i + "條內(nèi)容");
datas.add(model);
}
//創(chuàng)建布局管理
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
//創(chuàng)建適配器
adapter = new MyAdapter(R.layout.item_rv, datas);
//給RecyclerView設(shè)置適配器
recyclerView.setAdapter(adapter);
}
}
- OK,運(yùn)行,得到的效果就是上述的效果。
三、點(diǎn)擊事件
上文中描述的就是使用BaseRecyclerViewAdapterHelper最基本的用法,因?yàn)榕聞偨佑|Android的兄弟們不明朗或不相信這么簡(jiǎn)單的用法,所以做了上節(jié)簡(jiǎn)單的demo示例用法。
那么使用列表當(dāng)然少不了點(diǎn)擊事件,不論是整個(gè)條目的點(diǎn)擊事件還是條目中子控件的點(diǎn)擊事件,該適配器對(duì)點(diǎn)擊事件也是做了及簡(jiǎn)化的處理:
3.1 條目事件
Item的點(diǎn)擊事件
//條目點(diǎn)擊事件
adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "點(diǎn)擊了第" + (position + 1) + "條條目", Toast.LENGTH_SHORT).show();
}
});
Item的長(zhǎng)按事件
//條目長(zhǎng)按事件
adapter.setOnItemLongClickListener(new BaseQuickAdapter.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "長(zhǎng)按了第" + (position + 1) + "條條目", Toast.LENGTH_SHORT).show();
return false;
}
});
注意事項(xiàng)
- 在嵌套recycleView的情況下需要使用你使用 adapter. setOnItemClickListener 來(lái)設(shè)置點(diǎn)擊事件,如果使用recycleView.addOnItemTouchListener會(huì)累計(jì)添加的。
3.2 條目子控件事件
Item子控件的點(diǎn)擊事件
- 首先:在adapter的convert方法里面通過(guò) helper.addOnClickListener 綁定一下子控件的控件id
@Override
protected void convert(BaseViewHolder helper, Model item) {
//可鏈?zhǔn)秸{(diào)用賦值
helper.setText(R.id.tv_title, item.getTitle())
.setText(R.id.tv_content, item.getContent())
.addOnClickListener(R.id.iv_img) //給圖標(biāo)添加點(diǎn)擊事件
.setImageResource(R.id.iv_img, R.mipmap.ic_launcher);
//獲取當(dāng)前條目position
//int position = helper.getLayoutPosition();
}
- 然后:我們?cè)O(shè)置
//條目子控件點(diǎn)擊事件
adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "點(diǎn)擊了第" + (position + 1) + "條條目的圖片", Toast.LENGTH_SHORT).show();
}
});
Item子控件的長(zhǎng)按事件
這里和子控件點(diǎn)擊事件是一樣的,只是將 點(diǎn)擊 變成 長(zhǎng)按 就可以了
首先:在adapter的convert方法里面通過(guò) helper.addOnLongClickListener 綁定一下子控件的控件id
@Override
protected void convert(BaseViewHolder helper, Model item) {
//可鏈?zhǔn)秸{(diào)用賦值
helper.setText(R.id.tv_title, item.getTitle())
.setText(R.id.tv_content, item.getContent())
//.addOnClickListener(R.id.iv_img) //給圖標(biāo)添加點(diǎn)擊事件
.addOnLongClickListener(R.id.iv_img)//給圖片添加長(zhǎng)按事件
.setImageResource(R.id.iv_img, R.mipmap.ic_launcher);
//獲取當(dāng)前條目position
//int position = helper.getLayoutPosition();
}
- 然后:我們?cè)O(shè)置
//條目子控件長(zhǎng)按事件
adapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() {
@Override
public boolean onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "長(zhǎng)按了第" + (position + 1) + "條條目的圖片", Toast.LENGTH_SHORT).show();
return false;
}
});
注意事項(xiàng)
- 設(shè)置子控件的事件,如果不在adapter中綁定,點(diǎn)擊事件無(wú)法生效,因?yàn)闊o(wú)法找到你需要設(shè)置的控件。
多個(gè)Item子控件事件
官方文檔上沒(méi)說(shuō),但是 其實(shí)這里可以用很常規(guī)的方法處理,就是通過(guò)判斷ID來(lái)判定是否是我要的控件?,從而處理不同的事件
比如我這里給 圖片和標(biāo)題 都加點(diǎn)擊事件處理不同的邏輯
首先:當(dāng)然是在適配器中給圖片和標(biāo)題都添加點(diǎn)擊事件
@Override
protected void convert(BaseViewHolder helper, Model item) {
//可鏈?zhǔn)秸{(diào)用賦值
helper.setText(R.id.tv_title, item.getTitle())
.setText(R.id.tv_content, item.getContent())
.addOnClickListener(R.id.iv_img) //給圖標(biāo)添加 點(diǎn)擊事件
.addOnClickListener(R.id.tv_title) //給標(biāo)題也添加 點(diǎn)擊事件
.setImageResource(R.id.iv_img, R.mipmap.ic_launcher);
//獲取當(dāng)前條目position
//int position = helper.getLayoutPosition();
}
- 其次:
//條目子控件點(diǎn)擊事件
adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
//判斷id
if (view.getId() == R.id.iv_img) {
Log.i("tag", "點(diǎn)擊了第" + position + "條條目的 圖片");
} else if (view.getId() == R.id.tv_title) {
Log.i("tag", "點(diǎn)擊了第" + position + "條條目的 標(biāo)題");
}
}
});
- 那么如果是長(zhǎng)按事件呢?當(dāng)然也是相同的處理方法。
如果需要在子控件事件中獲取其他子控件可以使用:
getViewByPosition(RecyclerView recyclerView, int position, @IdRes int viewId)
比如:
//條目子控件點(diǎn)擊事件
adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "點(diǎn)擊了第" + (position + 1) + "條條目的圖片", Toast.LENGTH_SHORT).show();
TextView tv_title = (TextView) adapter.getViewByPosition(recyclerView, position, R.id.tv_title);
Log.i("tag", "當(dāng)前圖片對(duì)應(yīng)的 title=" + tv_title.getText());
}
});
//條目子控件長(zhǎng)按事件
adapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() {
@Override
public boolean onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "長(zhǎng)按了第" + position + "條條目的圖片", Toast.LENGTH_SHORT).show();
TextView tv_title = (TextView) adapter.getViewByPosition(recyclerView, position, R.id.tv_title);
Log.i("tag", "長(zhǎng)按的圖片對(duì)應(yīng)的title=" + tv_title.getText());
return false;
}
});
注意:如果有header的話需要處理一下position加上 headerlayoutcount。
- 最后,到這里,點(diǎn)擊事件基本就完了
四、添加列表加載動(dòng)畫(huà)
給條目添加動(dòng)畫(huà)也是比較常見(jiàn)的需求
開(kāi)啟動(dòng)畫(huà)(默認(rèn)為漸顯效果)
//開(kāi)啟動(dòng)畫(huà)(默認(rèn)為漸顯效果)
adapter.openLoadAnimation();
- 該適配器提供了5種動(dòng)畫(huà)效果(漸顯、縮放、從下到上,從左到右、從右到左)
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;
- 更換動(dòng)畫(huà)效果
//使用縮放動(dòng)畫(huà)
adapter.openLoadAnimation(BaseQuickAdapter.SCALEIN);
- 如果想自定義動(dòng)畫(huà),該適配器也提供了接口
//自定義動(dòng)畫(huà)效果
adapter.openLoadAnimation(new BaseAnimation() {
@Override
public Animator[] getAnimators(View view) {
return new Animator[]{
ObjectAnimator.ofFloat(view, "scaleY", 1, 0.5f, 1),
ObjectAnimator.ofFloat(view, "scaleX", 1, 0.5f, 1)
};
}
});
- 注意:動(dòng)畫(huà)默認(rèn)只執(zhí)行一次,如果想重復(fù)執(zhí)行可設(shè)置
//設(shè)置重復(fù)執(zhí)行動(dòng)畫(huà)
adapter.isFirstOnly(false);
- 設(shè)置不顯示動(dòng)畫(huà)數(shù)量(老實(shí)講,我沒(méi)明白這個(gè)方法的效果是啥,因?yàn)槲也](méi)有看到效果 0.0)
adapter.setNotDoAnimationCount(count);
- 首次到界面的item每次都依次執(zhí)行加載動(dòng)畫(huà)
由于進(jìn)入界面的item都是很多的速度進(jìn)來(lái)的所以不會(huì)出現(xiàn)滑動(dòng)顯示的依次執(zhí)行動(dòng)畫(huà)效果,這個(gè)時(shí)候會(huì)一起執(zhí)行動(dòng)畫(huà),如果覺(jué)得這樣的效果不好可以使用setNotDoAnimationCount設(shè)置第一屏item不執(zhí)行動(dòng)畫(huà),但是如果需要依次執(zhí)行動(dòng)畫(huà)可以重寫(xiě)startAnim讓第一個(gè)屏幕的item動(dòng)畫(huà)延遲執(zhí)行即可。
@Override
protected void startAnim(Animator anim, int index) {
super.startAnim(anim, index);
if (index < count)
anim.setStartDelay(index * 150);
}
五、添加頭部、尾部
- 添加
(可以添加多個(gè)頭部或尾部)
mQuickAdapter.addHeaderView(headerView);
mQuickAdapter.addFooterView(footerView);
- 刪除指定view
mQuickAdapter.removeHeaderView(headerView);
mQuickAdapter.removeFooterView(footerView);
- 刪除所有
mQuickAdapter.removeAllHeaderView();
mQuickAdapter.removeAllFooterView();
- 默認(rèn)出現(xiàn)了頭部就不會(huì)顯示Empty,和尾部,配置以下方法也支持同時(shí)顯示:
mQuickAdapter.setHeaderAndEmpty()
mQuickAdapter.setHeaderFooterEmpty();
- 默認(rèn)頭部尾部都是占滿一行,如果需要不占滿可以配置:
mQuickAdapter.setHeaderViewAsFlow();
mQuickAdapter.setFooterViewAsFlow();
六、上拉加載
沒(méi)錯(cuò),該適配器居然還實(shí)現(xiàn)了加載更多的功能,真心佩服作者
設(shè)置上拉加載
// 滑動(dòng)最后一個(gè)Item的時(shí)候回調(diào)onLoadMoreRequested方法
setOnLoadMoreListener(RequestLoadMoreListener);
- 默認(rèn)第一次加載會(huì)進(jìn)入回調(diào),如果不需要可以配置:
mQuickAdapter.disableLoadMoreIfNotFullPage();
- 回調(diào)處理代碼
//上拉加載(設(shè)置這個(gè)監(jiān)聽(tīng)就表示有上拉加載功能了)
mQuickAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
@Override public void onLoadMoreRequested() {
mRecyclerView.postDelayed(new Runnable() {
@Override
public void run() {
if (mCurrentCounter >= TOTAL_COUNTER) {
//數(shù)據(jù)全部加載完畢
mQuickAdapter.loadMoreEnd();
} else {
if (isErr) {
//成功獲取更多數(shù)據(jù)(可以直接往適配器添加數(shù)據(jù))
mQuickAdapter.addData(DataServer.getSampleData(PAGE_SIZE));
mCurrentCounter = mQuickAdapter.getData().size();
//主動(dòng)調(diào)用加載完成,停止加載
mQuickAdapter.loadMoreComplete();
} else {
//獲取更多數(shù)據(jù)失敗
isErr = true;
Toast.makeText(PullToRefreshUseActivity.this, R.string.network_err, Toast.LENGTH_LONG).show();
//同理,加載失敗也要主動(dòng)調(diào)用加載失敗來(lái)停止加載(而且該方法會(huì)提示加載失敗)
mQuickAdapter.loadMoreFail();
}
}
}
}, delayMillis);
}
}, mReyclerView);
- 加載完成(注意不是加載結(jié)束,而是本次數(shù)據(jù)加載結(jié)束并且還有下頁(yè)數(shù)據(jù))
mQuickAdapter.loadMoreComplete();
- 加載失敗
mQuickAdapter.loadMoreFail();
- 加載結(jié)束
mQuickAdapter.loadMoreEnd();
注意:如果上拉結(jié)束后,下拉刷新需要再次開(kāi)啟上拉監(jiān)聽(tīng),需要使用setNewData方法填充數(shù)據(jù)。
- 打開(kāi)或關(guān)閉加載(一般用于下拉的時(shí)候做處理,因?yàn)樯侠吕荒芡瑫r(shí)操作)
mQuickAdapter.setEnableLoadMore(boolean);
- 預(yù)加載(這個(gè)功能屌炸天)
// 當(dāng)列表滑動(dòng)到倒數(shù)第N個(gè)Item的時(shí)候(默認(rèn)是1)回調(diào)onLoadMoreRequested方法
mQuickAdapter.setPreLoadNumber(int);
設(shè)置自定義加載布局
mQuickAdapter.setLoadMoreView(new CustomLoadMoreView());
public final class CustomLoadMoreView extends LoadMoreView {
@Override
public int getLayoutId() {
return R.layout.view_load_more;
}
/**
* 如果返回true,數(shù)據(jù)全部加載完畢后會(huì)隱藏加載更多
* 如果返回false,數(shù)據(jù)全部加載完畢后會(huì)顯示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>
七、下拉加載(符合聊天軟件下拉歷史數(shù)據(jù)需求)
1、這里的下拉加載我個(gè)人覺(jué)得不好用,并不像我們常用的那種下拉加載(當(dāng)然有可能是我沒(méi)有合理使用)
2、所以如果是需要下拉刷新的功能,我個(gè)人會(huì)使用SwipeRefreshLayout
- 設(shè)置開(kāi)啟開(kāi)關(guān)
mAdapter.setUpFetchEnable(true);
- 設(shè)置監(jiān)聽(tīng)
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);
}
- 開(kāi)始加載的位置
mAdapter.setStartUpFetchPosition(2);
八、分組布局
由于我還沒(méi)碰到過(guò)這樣的需求,所以對(duì) 分組布局 這個(gè)概念還不是很了解;
當(dāng)然,我對(duì)概念都不知道,所以也不知道是什么樣的效果,當(dāng)然也就沒(méi)有寫(xiě)demo(主要是時(shí)間問(wèn)題)。
以下是正文
實(shí)體類必須繼承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構(gòu)造需要傳入兩個(gè)布局id,第一個(gè)是item的,第二個(gè)是head的,在convert方法里面加載item數(shù)據(jù),在convertHead方法里面加載head數(shù)據(jù):
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();
}
});
}
九、多布局
多種類型條目的列表是我們?nèi)粘i_(kāi)發(fā)中非常常見(jiàn)的功能,當(dāng)然該適配器也給我們提供了相應(yīng)的方法。
使用該適配器設(shè)置不同類型條目有兩種方式,一種 耦合了實(shí)體類,一種是設(shè)置代理,這里兩種方式我都會(huì)演示一遍。
9.1 實(shí)現(xiàn)MultiItemEntity的方式
- 實(shí)體類必須實(shí)現(xiàn)MultiItemEntity,在設(shè)置數(shù)據(jù)的時(shí)候,需要給每一個(gè)數(shù)據(jù)設(shè)置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;
}
}
- 在適配器構(gòu)造函數(shù)里面addItemType綁定type和layout的關(guān)系
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;
}
}
}
- 如果考慮到在GridLayoutManager復(fù)用item問(wèn)題可以配置
multipleItemAdapter.setSpanSizeLookup(new BaseQuickAdapter.SpanSizeLookup() {
@Override
public int getSpanSize(GridLayoutManager gridLayoutManager, int position) {
return data.get(position).getSpanSize();
}
});
如果使用多布局出現(xiàn)這個(gè)NotFoundException異常,有可能是addItemType()兩個(gè)參數(shù)寫(xiě)反了。
以上就是官方文檔給出的多布局設(shè)置方式的介紹,可能對(duì)于像我這樣Android小白會(huì)看的一臉懵逼,所以我寫(xiě)了一個(gè)很簡(jiǎn)單的Demo方便理解。
Demo實(shí)例演示
- 從上述文檔中我們是可以看出來(lái)兩點(diǎn)
1.我們的數(shù)據(jù)不直接傳給適配器,而是通過(guò)MultiItemEntity來(lái)傳遞
2.適配器構(gòu)造里必須綁定type和layout的關(guān)系
- 廢話不多說(shuō),演示一個(gè)小demo(這里Item布局我就不貼出來(lái)了)
第一步:創(chuàng)建MultiItemEntity類
public class MyMultipleItem implements MultiItemEntity {
public static final int FIRST_TYPE = 1;
public static final int SECOND_TYPE = 2;
public static final int NORMAL_TYPE = 3;
private int itemType;
private Model data;
public MyMultipleItem(int itemType, Model data) {
this.itemType = itemType;
this.data = data;
}
@Override
public int getItemType() {
return itemType;
}
public Model getData(){
return data;
}
}
第二步:創(chuàng)建適配器
public class MultipleItemAdapter extends BaseMultiItemQuickAdapter<MyMultipleItem, BaseViewHolder> {
public MultipleItemAdapter(List data) {
super(data);
//必須綁定type和layout的關(guān)系
addItemType(MyMultipleItem.FIRST_TYPE, R.layout.first_type_layout);
addItemType(MyMultipleItem.SECOND_TYPE, R.layout.second_type_layout);
addItemType(MyMultipleItem.NORMAL_TYPE, R.layout.item_rv);
}
@Override
protected void convert(BaseViewHolder helper, MyMultipleItem item) {
switch (helper.getItemViewType()) {
case MyMultipleItem.FIRST_TYPE:
Log.i("tag","FIRST_TYPE==============="+helper.getLayoutPosition());
break;
case MyMultipleItem.SECOND_TYPE:
Log.i("tag","SECOND_TYPE==============="+helper.getLayoutPosition());
break;
case MyMultipleItem.NORMAL_TYPE:
helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
.setText(R.id.tv_title, item.getData().getTitle())
.setText(R.id.tv_content, item.getData().getContent());
break;
}
}
}
第三步:最后當(dāng)然是在我們的Activity里編寫(xiě)代碼
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<Model> datas01;
private List<MyMultipleItem> datas02;
private MultipleItemAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化RecyclerView
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
//模擬的假數(shù)據(jù)(實(shí)際開(kāi)發(fā)中當(dāng)然是從網(wǎng)絡(luò)獲取數(shù)據(jù))
datas01 = new ArrayList<>();
Model model;
for (int i = 0; i < 30; i++) {
model = new Model();
model.setTitle("我是第" + i + "條標(biāo)題");
model.setContent("第" + i + "條內(nèi)容");
datas01.add(model);
}
datas02 = new ArrayList<>();
//這里我是隨機(jī)給某一條目加載不同的布局
for (int i = 0; i < 30; i++) {
if (i % 3 == 0) {
datas02.add(new MyMultipleItem(MyMultipleItem.FIRST_TYPE, null));
} else if (i % 7 == 0) {
datas02.add(new MyMultipleItem(MyMultipleItem.SECOND_TYPE, null));
} else {
datas02.add(new MyMultipleItem(MyMultipleItem.NORMAL_TYPE, datas01.get(i)));
}
}
//創(chuàng)建布局管理
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
//創(chuàng)建適配器
adapter = new MultipleItemAdapter(datas02);
//給RecyclerView設(shè)置適配器
recyclerView.setAdapter(adapter);
}
}
最后:已近完事了,可以運(yùn)行了,
9.2 為 BaseQuickAdapter 設(shè)置代理的方式
從上文中,我們可以知道,其實(shí)多布局的本質(zhì)還是要用某一個(gè)變量來(lái)區(qū)分,上述的方法是使用專門提供得MultiItemEntity接口,讓我們實(shí)現(xiàn),從而進(jìn)行區(qū)分;
既然是某一個(gè)變量來(lái)區(qū)分,那我們能不能不實(shí)現(xiàn)MultiItemEntity接口,直接在適配器里進(jìn)行區(qū)分呢?
官方還給出了一種方式,給BaseQuickAdapter 設(shè)置代理
首先我們先看一下官方給出的文檔(分三步)
public class MultiDelegateAdapter extends BaseQuickAdapter<Entity, BaseViewHolder> {
public MultiDelegateAdapter() {
super(null);
//Step.1
setMultiTypeDelegate(new MultiTypeDelegate<Entity>() {
@Override
protected int getItemType(Entity entity) {
//根據(jù)你的實(shí)體類來(lái)判斷布局類型
return entity.type;
}
});
//Step.2
getMultiTypeDelegate()
.registerItemType(Entity.TEXT, R.layout.item_text_view)
.registerItemType(Entity.IMG, R.layout.item_image_view);
}
@Override
protected void convert(BaseViewHolder helper, Entity entity) {
//Step.3
switch (helper.getItemViewType()) {
case Entity.TEXT:
// do something
break;
case Entity.IMG:
// do something
break;
}
}
}
- 可能對(duì)于剛接觸Android的朋友會(huì)看的很懵逼,完全不知道該怎么用,那是因?yàn)槲覀儗?duì)面向?qū)ο蟮乃枷肜斫獾倪€不是很深刻,沒(méi)關(guān)系,這里我也寫(xiě)一個(gè)簡(jiǎn)單的Demo,希望能夠幫助理解;
Demo實(shí)例演示
首先:我給Model類添加一個(gè)變量來(lái)區(qū)分類型
public class Model {
public static final int FIRST_TYPE = 1;
public static final int SECOND_TYPE = 2;
public static final int NORMAL_TYPE = 3;
//添加類型變量
public int type;
private String title;
private String content;
private String imgUrl;
//set get方法
......
}
其次:編寫(xiě)適配器
public class MultiDelegateAdapter extends BaseQuickAdapter<Model, BaseViewHolder> {
public MultiDelegateAdapter(@Nullable List<Model> data) {
super(data);
setMultiTypeDelegate(new MultiTypeDelegate<Model>() {
@Override
protected int getItemType(Model entity) {
//根據(jù)你的實(shí)體類來(lái)判斷布局類型
return entity.type;
}
});
getMultiTypeDelegate()
.registerItemType(Model.FIRST_TYPE, R.layout.first_type_layout)
.registerItemType(Model.SECOND_TYPE, R.layout.second_type_layout)
.registerItemType(Model.NORMAL_TYPE, R.layout.item_rv);
}
@Override
protected void convert(BaseViewHolder helper, Model item) {
switch (helper.getItemViewType()) {
case Model.FIRST_TYPE:
Log.i("tag", "FIRST_TYPE===============" + helper.getLayoutPosition());
break;
case Model.SECOND_TYPE:
Log.i("tag", "SECOND_TYPE===============" + helper.getLayoutPosition());
break;
case Model.NORMAL_TYPE:
helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
.setText(R.id.tv_title, item.getTitle())
.setText(R.id.tv_content, item.getContent());
break;
}
}
}
最后:在Activity中使用該適配器
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<Model> datas;
private MultiDelegateAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化RecyclerView
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
//模擬的假數(shù)據(jù)(實(shí)際開(kāi)發(fā)中當(dāng)然是從網(wǎng)絡(luò)獲取數(shù)據(jù))
datas = new ArrayList<>();
Model model;
for (int i = 0; i < 30; i++) {
model = new Model();
model.setTitle("我是第" + i + "條標(biāo)題");
model.setContent("第" + i + "條內(nèi)容");
//這里隨機(jī)給某一條目設(shè)置不同布局類型
if (i == 3 || i == 10 || i == 13) {
model.setType(Model.FIRST_TYPE);
} else if (i == 5 || i == 8 || i == 20) {
model.setType(Model.SECOND_TYPE);
} else {
model.setType(Model.NORMAL_TYPE);
}
datas.add(model);
}
//創(chuàng)建布局管理
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
//創(chuàng)建適配器
adapter = new MultiDelegateAdapter(datas);
//給RecyclerView設(shè)置適配器
recyclerView.setAdapter(adapter);
}
}
- OK,運(yùn)行結(jié)果應(yīng)該是:第4、11、14條目顯示的是同一種類型布局,第6、9、21條目顯示的是另一種類型布局,其它條目顯示的又是另一種類型布局。
十、設(shè)置空布局
- 就一個(gè)方法,沒(méi)有數(shù)據(jù)時(shí)就默認(rèn)顯示該布局:
// 沒(méi)有數(shù)據(jù)的時(shí)候默認(rèn)顯示該布局
mQuickAdapter.setEmptyView(getView());
- 設(shè)置空布局就一個(gè)方法,但是我還是單獨(dú)放在一個(gè)章節(jié)里來(lái)講也是有原因的:
1、在我寫(xiě)demo的時(shí)候,設(shè)置空布局時(shí)就碰上一個(gè)bug,即使我所有的布局中的相關(guān)控件高度都設(shè)置成填充父窗體,但是顯示時(shí)并沒(méi)有填充整個(gè)界面,而僅僅是包裹內(nèi)容;
2、但是當(dāng)我在我現(xiàn)在開(kāi)發(fā)的項(xiàng)目中設(shè)置空布局時(shí)又確實(shí)是填充整個(gè)父窗體,這就讓我很迷惑了,狂躁了好久,看了好多次demo代碼也沒(méi)找到原因,最后還是沒(méi)有找到原因.......
3、當(dāng)然,因?yàn)槲乙彩且粋€(gè)Android新人,有很多地方我都需要繼續(xù)努力學(xué)習(xí),或許只是一個(gè)很簡(jiǎn)單、很低級(jí)的錯(cuò)誤導(dǎo)致沒(méi)有填充父窗體,如果看到這篇文章的朋友你也遇到過(guò)相似的問(wèn)題,而且又正好知道原因,還希望你能多多指教。
十一、添加拖拽、滑動(dòng)刪除
本人測(cè)試了一下,拖拽交換位置效果不錯(cuò),但是滑動(dòng)刪除效果我測(cè)試的是有問(wèn)題的,條目顯示會(huì)錯(cuò)亂,被刪除的條目還會(huì)出現(xiàn)空白條目現(xiàn)象.......(疑惑)不知道是不是我打開(kāi)方式不對(duì)???
拖拽和滑動(dòng)刪除的回調(diào)方法:
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);
// 開(kāi)啟拖拽
mAdapter.enableDragItem(itemTouchHelper, R.id.textView, true);
mAdapter.setOnItemDragListener(onItemDragListener);
// 開(kāi)啟滑動(dòng)刪除
mAdapter.enableSwipeItem();
mAdapter.setOnItemSwipeListener(onItemSwipeListener);
- 默認(rèn)不支持多個(gè)不同的 ViewType 之間進(jìn)行拖拽,如果開(kāi)發(fā)者有所需求:
重寫(xiě) ItemDragAndSwipeCallback 里的onMove()方法,return true即可
十二、樹(shù)形列表
本人表示不想貼出demo了,因?yàn)槲覜](méi)嘗試著寫(xiě)demo試試,看文檔說(shuō)明感覺(jué)和多布局貌似是一個(gè)套路....(感覺(jué)是...)
例子:三級(jí)菜單
// 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;
}
}
- 開(kāi)啟所有菜單:
adapter.expandAll();
- 刪除某一個(gè)item(添加和修改的思路是一樣的)
// 獲取當(dāng)前父級(jí)位置
int cp = getParentPosition(person);
// 通過(guò)父級(jí)位置找到當(dāng)前l(fā)ist,刪除指定下級(jí)
((Level1Item)getData().get(cp)).removeSubItem(person);
// 列表層刪除相關(guān)位置的數(shù)據(jù)
getData().remove(holder.getLayoutPosition());
// 更新視圖
notifyDataSetChanged();
十三、自定義ViewHolder
- 需要繼承BaseViewHolder
public class MovieViewHolder extends BaseViewHolder
- 然后修改adapter的第二個(gè)泛型為自定義的ViewHolder
public class DataBindingUseAdapter extends BaseQuickAdapter<Movie, DataBindingUseAdapter.MovieViewHolder>
-
注意:需要單獨(dú)建一個(gè)外部類繼承BaseViewHolder,否則部分機(jī)型會(huì)出現(xiàn)ClassCastException,如果是內(nèi)部類的構(gòu)造方法要是public,定義的那個(gè)類也最好是public。
十三、擴(kuò)展框架
由于adapter本身能力有限,我們又不想耦合view層所以有些需求是現(xiàn)實(shí)不了,于是合作了一些優(yōu)秀開(kāi)源庫(kù),為開(kāi)發(fā)者提供更多可能性。以下擴(kuò)展框架都是有結(jié)合BRVAH的demo。
結(jié)束語(yǔ)
我個(gè)人建議讀者關(guān)注作者原文,原文作者是會(huì)持續(xù)更新的;
官方網(wǎng)站:www.recyclerview.org
Demo下載地址:http://fir.im/s91g
如果有問(wèn)題:提問(wèn),請(qǐng)先看這個(gè)!

