一族展、RecyclerView 介紹
在 RecyclerView 出來之前,大家都在使用 ListView楣富、GridView访忿,當(dāng)然 RecyclerView 出來之后瞧栗,基本上都轉(zhuǎn)向了 RecyclerView,從名字上可以看出海铆,它能夠?qū)崿F(xiàn)view 的復(fù)用迹恐,同樣 ListView 在使用時我們自己也可以通過 converView 來實現(xiàn)復(fù)用,但是 RecyclerView 已經(jīng)幫我們做好了卧斟,我們只需要給出需要裝載 view 的 ViewHolder 就行殴边,同時允許我們添加 ItemDecoration,ItemAnimator珍语,設(shè)置多樣的LayoutManager锤岸。
有這樣一段英文:
the RecyclerView itself doesn't care about visuals at all. It doesn't care about placing the elements at the right place, it doesn't care about separating any items and not about the look of each individual item either. To exaggerate a bit: All RecyclerView does, is recycle stuff. Hence the name.
Anything that has to do with layout, drawing and so on, that is anything that has to do with how your data set is presented, is delegated to pluggable classes. That makes the new RecyclerView API extremely flexible.
大概意思是:RecyclerView 不關(guān)心視圖,不關(guān)心元素位置板乙,只關(guān)心復(fù)用是偷,也就是說 RecyclerView 幫我們做好了復(fù)用的實現(xiàn), 其他方面我們自己來配置,這樣更加靈活晓猛,達到所謂的“插拔式”效果饿幅,即可添加 分割線,可添加動畫效果戒职,可設(shè)置布局效果等等。
其實復(fù)用透乾,就是多創(chuàng)建一個或者幾個視圖洪燥,當(dāng)滑動到底部時,加載更多的視圖時乳乌,復(fù)用已經(jīng)不可見的視圖捧韵。至于多創(chuàng)建視圖是幾個,每一屏上的視圖是幾個汉操,暫時我也不知道再来,后面進行源碼分析時,再來揭曉磷瘤,先知道它大概的原理芒篷。
二、基本使用
RecyclerView 使用很頻繁采缚,其實主要場景就是要展示的視圖數(shù)量很多针炉,無法在一個屏幕之下展示出來,當(dāng)然你想到 ScrollView扳抽,ScrollView 展示的視圖也是有限的篡帕,也不會很多,否則會造成卡頓贸呢,甚至 OOM镰烧。RecyclerView 一般的用法就是做分頁加載,上拉加載楞陷,下拉刷新怔鳖,然后將數(shù)據(jù)鋪到界面上,復(fù)雜一些時猜谚,界面上有幾種不同的視圖败砂,通過不同的 ViewHolder 來裝載。
下面就先開看看最基本的用法:
首先在 gradle 中引入 RecyclerView 魏铅,這里同時引入 cardview昌犹,不是必須的,只是為了在添加每個Item 視圖時使用 cardview 览芳,更加美觀一些斜姥。
implementation 'com.android.support:cardview-v7:27.1.1'
implementation 'com.android.support:recyclerview-v7:27.1.1'
1. XML 布局設(shè)置
引用 recyclerview
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:toolbar="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--使用 toolbar 設(shè)置菜單-->
<include layout="@layout/view_toolbar" />
<!--用于刷新-->
<com.ralf.www.recyclerviewtest.widget.MultiSwipeRefreshLayout
android:id="@+id/swipe_refresh_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<!--重點部分,使用RecyclerView,高度設(shè)置铸敏,-->
<!--如果是垂直布局缚忧,使用match_parent-->
<!--如果是水平布局,可使用wrap_content -->
<android.support.v7.widget.RecyclerView
android:id="@+id/rv_meizhi"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.ralf.www.recyclerviewtest.widget.MultiSwipeRefreshLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/main_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right|bottom"
android:layout_marginBottom="24dp"
android:layout_marginRight="16dp"
android:clickable="true"
android:onClick="onFab"
android:src="@mipmap/ic_refresh_white_24dp"
app:borderWidth="0dp"
app:elevation="4dp"
app:layout_anchor="@id/swipe_refresh_layout"
app:layout_anchorGravity="right|bottom"
app:layout_behavior="com.ralf.www.recyclerviewtest.widget.FABAutoHideBehavior" />
</android.support.design.widget.CoordinatorLayout>
</FrameLayout>
子項的 XML 布局
item_main_activity_rv.xml
子項布局在重寫 Adapter 時使用,外面包了一層 CardView杈笔,更加美觀一些
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
android:id="@+id/meizhi_card"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:clickable="true"
android:foreground="?attr/selectableItemBackground"
app:cardCornerRadius="2dp"
app:cardElevation="4dp"
tools:minWidth="160dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/iv_meizhi"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:scaleType="fitXY"
tools:src="@mipmap/ic_launcher"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="10dp">
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
tools:text="Title"/>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
2. LayoutManager 設(shè)置
RecyclerView 設(shè)置 LayoutManager闪水,系統(tǒng)提供了3種 LayoutManager:LinearLayoutManager(線性布局)、GridLayoutManager(網(wǎng)格布局)蒙具、StaggeredGridLayoutManager(瀑布布局)球榆,根據(jù)場景需要自己設(shè)定,也可以自己實現(xiàn) LayoutManager禁筏。
這里先給出簡單的使用持钉,對于詳細的分析以及自己實現(xiàn) 后面的文章(加粗下,提醒自己篱昔。每强。)再給出分析。
// final StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(
// 2, StaggeredGridLayoutManager.VERTICAL
// );
final GridLayoutManager layoutManager = new GridLayoutManager(this, 2
, GridLayoutManager.VERTICAL, false);
// final LinearLayoutManager layoutManager = new LinearLayoutManager(this,
// LinearLayoutManager.VERTICAL, false);
// 設(shè)置布局管理器
mRecyclerView.setLayoutManager(layoutManager);
3. 設(shè)置數(shù)據(jù)源
數(shù)據(jù)源一般就用 List 傳遞給 Adapter州刽,作為其構(gòu)造函數(shù)中的一個參數(shù)給出空执。
private List<GanHuo> mPicList = new ArrayList<>();
直接在 成員變量聲明時給出 List 對象,一般數(shù)據(jù)源 都是通過網(wǎng)絡(luò)求來的怀伦,在 demo 中我集成了 Retrofit + Rxjava 進行網(wǎng)絡(luò)請求脆烟,加載圖片,對這部分沒接觸的童鞋可以看看 Retrofit + Rxjava房待,或者自己替換成本地的圖片也行邢羔。
有一點,需要注意下:對于初始化時桑孩,設(shè)置布局管理器拜鹤,設(shè)置 Adapter,設(shè)置數(shù)據(jù)源流椒,沒有嚴格的先后順序敏簿。開始沒有在 List 中加入數(shù)據(jù),初始化時候宣虾,加載網(wǎng)絡(luò)數(shù)據(jù)或者本地數(shù)據(jù)惯裕,然后通過刷新列表即可。
4. 實現(xiàn) Adapter 并設(shè)置
PictureAdapter 需要集成 RecyclerView.Adapter绣硝,并需要聲明泛型類型 ViewHolder蜻势,否則是 Object 類型,并實現(xiàn) 三個方法:
生成用于持有每個 View 的 ViewHolder鹉胖,實現(xiàn)復(fù)用
onCreateViewHolder將ViewHolder綁定握玛,即將數(shù)據(jù)綁定到視圖上
onBindViewHolder獲取子 View 的數(shù)量够傍,即傳過來的 List 的大小
getItemCount
public class PictureAdapter extends RecyclerView.Adapter<PictureAdapter.PictureViewHolder> {
private List<GanHuo> picList;
private ItemClickListener mClickListener;
private Context mContext;
public PictureAdapter(Context context, List<GanHuo> picList) {
this.picList = picList;
mContext = context;
}
@NonNull
@Override
public PictureViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
// 加載布局 item_main_activity_rv.xml
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_main_activity_rv,
parent, false);
return new PictureViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull final PictureViewHolder holder, final int position) {
if (picList != null) {
GanHuo ganHuo = picList.get(position);
String url = ganHuo.getUrl();
String who = ganHuo.getWho();
// Glide圖片加載
RequestOptions requestOptions = RequestOptions.placeholderOf(R.mipmap.ic_launcher)
.error(R.mipmap.ic_refresh_white_24dp)
.useAnimationPool(true);
Glide.with(mContext)
.load(url)
.apply(requestOptions)
.into(holder.imageView);
holder.textView.setText(who);
// 點擊事件
if (mClickListener != null) {
holder.textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mClickListener.onItemClick(PictureAdapter.this, v, position);
}
});
holder.textView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mClickListener.onItemLongClick(PictureAdapter.this, v, position);
return true;
}
});
holder.imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mClickListener.onItemClick(PictureAdapter.this, v, position);
}
});
holder.imageView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mClickListener.onItemLongClick(PictureAdapter.this, v, position);
return true;
}
});
}
} else {
// default url
RequestOptions requestOptions = RequestOptions.placeholderOf(R.mipmap.ic_launcher)
.error(R.mipmap.ic_refresh_white_24dp);
Glide.with(mContext)
.load(R.mipmap.ic_launcher)
.apply(requestOptions)
.into(holder.imageView);
holder.textView.setText("看不到的妹紙");
}
}
@Override
public int getItemCount() {
if (picList == null || picList.size() < 1) {
return 0;
}
return picList.size();
}
// ViewHolder 用于存放 View,這個View也就是 前面設(shè)置的 .xml挠铲,RecyclerView 通過 ViewHolder進行復(fù)用
static class PictureViewHolder extends RecyclerView.ViewHolder {
private View mView;
private ImageView imageView;
private TextView textView;
public PictureViewHolder(View itemView) {
super(itemView);
mView = itemView;
initView();
}
private void initView() {
imageView = mView.findViewById(R.id.iv_meizhi);
textView = mView.findViewById(R.id.tv_title);
}
}
public ItemClickListener getClickListener() {
return mClickListener;
}
public void setClickListener(ItemClickListener clickListener) {
mClickListener = clickListener;
}
// 點擊事件接口
public interface ItemClickListener {
void onItemClick(RecyclerView.Adapter adapter,
View view, int position);
void onItemLongClick(RecyclerView.Adapter adapter,
View view, int position);
}
}
實現(xiàn) Adapter 之后冕屯,創(chuàng)建實例,然后用 RecyclerView setAdapter拂苹。
// mPicList 就是用于存放數(shù)據(jù)的
mAdapter = new PictureAdapter(this, mPicList);
mRecyclerView.setAdapter(mAdapter);
請求數(shù)據(jù)安聘,通過網(wǎng)絡(luò)請求數(shù)據(jù) Retrofit + Rxjava
private void requestData(int index) {
// 開始刷新
setRefreshing(true);
RetrofitClient.getRetrofitClientInstance()
.requestNetForData(NetApi.BASE_URL, RequestDataService.class)
.getGanHuoData("福利", 10, index)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(new DefaultObserver<BaseEntity<GanHuo>>() {
@Override
public void onNext(BaseEntity<GanHuo> ganHuo) {
if (ganHuo != null && !ganHuo.isError()) {
// 刷新列表
refreshRecyclerView(ganHuo.getResults());
}
}
@Override
public void onError(Throwable e) {
setRefreshing(false);
}
@Override
public void onComplete() {
// 刷新結(jié)束,如果是下拉刷新瓢棒,滾動到最頂部
setRefreshing(false);
if (mIndex == 1) {
mRecyclerView.smoothScrollToPosition(0);
}
}
});
}
刷新 RecyclerView搞挣,有上拉加載和下拉刷新,對于不同的情況做了簡單的處理音羞,防止更新數(shù)據(jù)時,有閃爍現(xiàn)象仓犬。
private void refreshRecyclerView(List<GanHuo> ganHuoList) {
if (ganHuoList == null || ganHuoList.size() < 1) {
return;
}
if (mIndex == 1) {
mPicList.clear();
mPicList.addAll(ganHuoList);
mAdapter.notifyDataSetChanged();
} else {
mPicList.addAll(ganHuoList);
mAdapter.notifyItemRangeInserted(mAdapter.getItemCount()
, ganHuoList.size());
}
}
此時嗅绰,就可以運行,能夠看到加載的圖片列表了搀继。
這里面有 RecyclerView的滑動事件監(jiān)聽窘面,用來滑動到底部時,做上拉加載更多數(shù)據(jù)叽躯。
mRecyclerView.addOnScrollListener(
new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView rv, int dx, int dy) {
// 判斷是否滑動到最底部
if (!mSwipeRefreshLayout.isRefreshing() &&
layoutManager.findLastCompletelyVisibleItemPosition() >= mAdapter.getItemCount() - 1) {
mIndex += 1;
requestData(mIndex);
}
}
}
);
5. 設(shè)置分割線
在圖中看出财边,已經(jīng)設(shè)置了分割線。設(shè)置分割線相對簡單点骑,主要有兩種方法:
- 在 xml 布局中添加 0.5 dp的View
- 使用 ItemDecoration
第一種就不介紹了酣难,自己嘗試弄一下。第二種實現(xiàn)ItemDecoration黑滴,系統(tǒng)默認只給出來了一種實現(xiàn) DividerItemDecoration憨募,
一般情況下,也夠用了袁辈,除了改一下顏色之類的菜谣。
// 添加分割線
// 第二個參數(shù)設(shè)置方向,垂直布局設(shè)置垂直分割線晚缩,水平布局設(shè)置水平分割線
mRecyclerView.addItemDecoration(new DividerItemDecoration(
this, DividerItemDecoration.VERTICAL));
分割線的顏色怎么修改的尾膊?
DividerItemDecoration 的顏色采用的應(yīng)是主題的顏色,所以要改變顏色荞彼,需要自己定義一個顏色冈敛,然后加到應(yīng)用的主題中。
在 drawable 中定義一個 shape
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
// 漸變顏色
<gradient
android:centerColor="#ff00ff00"
android:endColor="#ff0000ff"
android:startColor="#ffff0000"
android:type="linear" />
// 分割線寬度
<size android:height="4dp"/>
</shape>
加到應(yīng)用主題中
<!-- Application theme. -->
<style name="AppTheme1" parent="Theme.AppCompat.NoActionBar">
<item name="colorPrimary">@color/theme_primary</item>
<item name="colorPrimaryDark">@color/theme_primary_dark</item>
<item name="colorAccent">@color/md_red_400</item>
// 分割線主題顏色設(shè)置
<item name="android:listDivider">@drawable/divider_shape</item>
<!-- 加入toolbar溢出【彈出】菜單的風(fēng)格 -->
<item name="actionOverflowMenuStyle">@style/OverflowMenuStyle</item>
</style>
這樣就能改變分割線顏色卿泽,同時寬度也可以 shape 中修改
6. 設(shè)置動畫
設(shè)置動畫相對比較簡單莺债,調(diào)用 setItemAnimator 即可滋觉,動畫可以使用提供的默認的動畫,或者自己實現(xiàn) RecyclerView.ItemAnimator齐邦,達到自己想要的結(jié)果椎侠。
// 添加動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
動畫用于數(shù)據(jù)改變時顯示的動畫效果,這里在 Adapter 中添加兩個方法措拇,測試一下
/**
* 添加測試我纪,用于展現(xiàn)動畫
* @param position
*/
public void indertTest(int position){
picList.add(1,new GanHuo());
// 這里更新數(shù)據(jù)集不是用adapter.notifyDataSetChanged()而是
// notifyItemInserted(position)與notifyItemRemoved(position)
// 否則沒有動畫效果。
notifyItemInserted(position);
}
/**
* 刪除測試丐吓,用于展現(xiàn)動畫
* @param position
*/
public void removeTest(int position){
picList.remove(picList.get(position));
notifyItemRemoved(position);
}
想看看到更多的動畫效果浅悉,可以參考 github 上的開源項目 RecyclerViewItemAnimators
7. 設(shè)置點擊事件
對于 RecyclerView 的點擊事件,系統(tǒng)沒有提供接口 ClickListener和 LongClickListener券犁,需要自己實現(xiàn)术健。常用的方式一般有兩種:
- mRecyclerView.addOnItemTouchListener(listener);根據(jù)手勢動作判斷
- 第二種是自己在 Adapter 中設(shè)置接口,然后將實現(xiàn)傳遞進去
這里給出第二種的方式粘衬,看一下效果:
在 onBindViewHolder 方法中代碼有點多荞估,主要看setOnClickListener 部分,實際上還是給普通的控件設(shè)置點擊事件稚新,在 onClick 中回調(diào)我們設(shè)置的接口勘伺,這樣執(zhí)行的方法就是我們想要的動作了。
public class PictureAdapter extends RecyclerView.Adapter<PictureAdapter.PictureViewHolder> {
...
@Override
public void onBindViewHolder(@NonNull final PictureViewHolder holder, final int position) {
if (picList != null) {
GanHuo ganHuo = picList.get(position);
String url = ganHuo.getUrl();
String who = ganHuo.getWho();
RequestOptions requestOptions = RequestOptions.placeholderOf(R.mipmap.ic_launcher)
.error(R.mipmap.ic_refresh_white_24dp)
.useAnimationPool(true);
Glide.with(mContext)
.load(url)
.apply(requestOptions)
.into(holder.imageView);
holder.textView.setText(who);
// 點擊事件
if (mClickListener != null) {
holder.textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mClickListener.onItemClick(PictureAdapter.this, v, position);
}
});
holder.textView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mClickListener.onItemLongClick(PictureAdapter.this, v, position);
return true;
}
});
holder.imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mClickListener.onItemClick(PictureAdapter.this, v, position);
}
});
holder.imageView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
mClickListener.onItemLongClick(PictureAdapter.this, v, position);
return true;
}
});
}
} else {
// default url
RequestOptions requestOptions = RequestOptions.placeholderOf(R.mipmap.ic_launcher)
.error(R.mipmap.ic_refresh_white_24dp);
Glide.with(mContext)
.load(R.mipmap.ic_launcher)
.apply(requestOptions)
.into(holder.imageView);
holder.textView.setText("看不到的妹紙");
}
}
// ClickListener和 LongClickListener 回調(diào)接口
public interface ItemClickListener {
void onItemClick(RecyclerView.Adapter adapter,
View view, int position);
void onItemLongClick(RecyclerView.Adapter adapter,
View view, int position);
}
在 MainActivity 中添加接口的具體實現(xiàn)
// 設(shè)置點擊事件
mAdapter.setClickListener(new PictureAdapter.ItemClickListener() {
@Override
public void onItemClick(RecyclerView.Adapter adapter, View view, int position) {
GanHuo ganHuo = mPicList.get(position);
String who = ganHuo.getWho();
String desc = ganHuo.getDesc();
if (view.getId() == R.id.tv_title) {
Toast.makeText(MainActivity.this, "你點擊了" + who, Toast.LENGTH_SHORT).show();
} else if (view.getId() == R.id.iv_meizhi) {
Toast.makeText(MainActivity.this, "你點擊了" + desc, Toast.LENGTH_SHORT).show();
}
}
@Override
public void onItemLongClick(RecyclerView.Adapter adapter, View view, int position) {
GanHuo ganHuo = mPicList.get(position);
String who = ganHuo.getWho();
String desc = ganHuo.getDesc();
if (view.getId() == R.id.tv_title) {
Toast.makeText(MainActivity.this, "你長按了" + who, Toast.LENGTH_SHORT).show();
} else if (view.getId() == R.id.iv_meizhi) {
Toast.makeText(MainActivity.this, "你長按了" + desc, Toast.LENGTH_SHORT).show();
}
}
});
這樣執(zhí)行的方法就是我們想要的效果了褂删。
總結(jié)
主要介紹了 RecyclerView 的主要特點飞醉,與ListView 相比的優(yōu)勢,在于 view 的復(fù)用上屯阀;同時詳細介紹了基本的使用方法缅帘。使用 RecyclerView 可以很方便的使用 動畫,分割線蹲盘,以及布局管理器的樣式改變股毫。例子中的也給出了上拉加載和下拉刷新,但是僅供參看召衔,使用起來還是存在一定問題的铃诬,后面會做修正,在項目中使用的話苍凛,可以使用開源框架 Android智能下拉刷新框架-SmartRefreshLayout
好了趣席,就到這里,對 RecyclerView 不熟悉的趕緊試試吧醇蝴,后面會再寫一篇宣肚,介紹使用過程中的一些小細節(jié),避免采坑悠栓!
參考
Android RecyclerView 使用完全解析 體驗藝術(shù)般的控件