一筷狼、前言:
1. RecyclerView是什么
從Android 5.0開始棍潘,谷歌公司推出了一個用于大量數(shù)據(jù)展示的新控件RecylerView恃鞋,可以用來代替?zhèn)鹘y(tǒng)的ListView,更加強大和靈活亦歉。
RecyclerView是support-v7包中的新組件恤浪,是一個強大的滑動組件,與經(jīng)典的ListView相比肴楷,同樣擁有item回收復(fù)用的功能水由,這一點從它的名字Recyclerview即回收view也可以看出。
2. RecyclerView的優(yōu)點
RecyclerView并不會完全替代ListView(這點從ListView沒有被標(biāo)記為@Deprecated可以看出)赛蔫,兩者的使用場景不一樣砂客。但是RecyclerView的出現(xiàn)會讓很多開源項目被廢棄,例如橫向滾動的ListView, 橫向滾動的GridView, 瀑布流控件呵恢,因為RecyclerView能夠?qū)崿F(xiàn)所有這些功能鞠值。
比如:有一個需求是屏幕豎著的時候的顯示形式是ListView,屏幕橫著的時候的顯示形式是2列的GridView渗钉,此時如果用RecyclerView彤恶,則通過設(shè)置LayoutManager一行代碼實現(xiàn)替換。
RecylerView相對于ListView的優(yōu)點羅列如下:
RecyclerView封裝了viewholder的回收復(fù)用,也就是說RecyclerView標(biāo)準(zhǔn)化了ViewHolder声离,編寫Adapter面向的是ViewHolder而不再是View了歇竟,復(fù)用的邏輯被封裝了,寫起來更加簡單抵恋。
直接省去了listview中convertView.setTag(holder)和convertView.getTag()這些繁瑣的步驟焕议。提供了一種插拔式的體驗,高度的解耦弧关,異常的靈活盅安,針對一個Item的顯示RecyclerView專門抽取出了相應(yīng)的類,來控制Item的顯示世囊,使其的擴展性非常強别瞭。
-
設(shè)置布局管理器以控制Item的布局方式,橫向株憾、豎向以及瀑布流方式
例如:你想控制橫向或者縱向滑動列表效果可以通過LinearLayoutManager這個類來進行控制
(與GridView效果對應(yīng)的是GridLayoutManager,
與瀑布流對應(yīng)的還StaggeredGridLayoutManager等)蝙寨。
也就是說RecyclerView不再拘泥于ListView的線性展示方式,它也可以實現(xiàn)GridView的效果等多種效果嗤瞎。 可設(shè)置Item的間隔樣式(可繪制)
通過繼承RecyclerView的ItemDecoration這個類墙歪,然后針對自己的業(yè)務(wù)需求去書寫代碼。可以控制Item增刪的動畫贝奇,可以通過ItemAnimator這個類進行控制虹菲,當(dāng)然針對增刪的動畫,RecyclerView有其自己默認(rèn)的實現(xiàn)掉瞳。
但是關(guān)于Item的點擊和長按事件毕源,需要用戶自己去實現(xiàn)。
二陕习、 基本使用
1. 依賴:
//recyclerview依賴
implementation 'com.android.support:recyclerview-v7:27.1.0'
//開源動畫
implementation 'jp.wasabeef:recyclerview-animators:2.2.4'
2. 使用范例:
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this );
//設(shè)置布局管理器
recyclerView.setLayoutManager(layoutManager);
//設(shè)置為垂直布局霎褐,這也是默認(rèn)的
layoutManager.setOrientation(OrientationHelper. VERTICAL);
//設(shè)置Adapter
recyclerView.setAdapter(recycleAdapter);
//設(shè)置分隔線
recyclerView.addItemDecoration( new DividerGridItemDecoration(this ));
//設(shè)置增加或刪除條目的動畫
recyclerView.setItemAnimator( new DefaultItemAnimator());
在使用RecyclerView時候,必須指定一個適配器Adapter和一個布局管理器LayoutManager该镣。適配器繼承RecyclerView.Adapter類冻璃,具體實現(xiàn)類似ListView的適配器,取決于數(shù)據(jù)信息以及展示的UI拌牲。布局管理器用于確定RecyclerView中Item的展示方式以及決定何時復(fù)用已經(jīng)不可見的Item俱饿,避免重復(fù)創(chuàng)建以及執(zhí)行高成本的findViewById()方法歌粥。
可以看見RecyclerView相比ListView會多出許多操作塌忽,這也是RecyclerView靈活的地方,它將許多動能暴露出來失驶,用戶可以選擇性的自定義屬性以滿足需求土居。
3. 創(chuàng)建適配器
標(biāo)準(zhǔn)實現(xiàn)步驟如下:
① 創(chuàng)建Adapter:創(chuàng)建一個繼承RecyclerView.Adapter<VH>的Adapter類(VH是ViewHolder的類名)
② 創(chuàng)建ViewHolder:在Adapter中創(chuàng)建一個繼承RecyclerView.ViewHolder的靜態(tài)內(nèi)部類,記為VH。ViewHolder的實現(xiàn)和ListView的ViewHolder實現(xiàn)幾乎一樣擦耀。
③ 在Adapter中實現(xiàn)3個方法:
- onCreateViewHolder()
這個方法主要生成為每個Item inflater出一個View棉圈,但是該方法返回的是一個ViewHolder。該方法把View直接封裝在ViewHolder中眷蜓,然后我們面向的是ViewHolder這個實例分瘾,當(dāng)然這個ViewHolder需要我們自己去編寫。
需要注意的是在onCreateViewHolder()中吁系,映射Layout必須為
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);
而不能是:
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, null);
onBindViewHolder()
這個方法主要用于適配渲染數(shù)據(jù)到View中德召。方法提供給你了一viewHolder而不是原來的convertView。getItemCount()
這個方法就類似于BaseAdapter的getCount方法了汽纤,即總共有多少個條目上岗。
可以看出,RecyclerView將ListView中g(shù)etView()的功能拆分成了onCreateViewHolder()和onBindViewHolder()蕴坪。
基本的Adapter實現(xiàn)如下:
// ① 創(chuàng)建Adapter
public class NormalAdapter extends RecyclerView.Adapter<NormalAdapter.VH>{
//② 創(chuàng)建ViewHolder
public static class VH extends RecyclerView.ViewHolder{
public final TextView title;
public VH(View v) {
super(v);
title = (TextView) v.findViewById(R.id.title);
}
}
private List<String> mDatas;
public NormalAdapter(List<String> data) {
this.mDatas = data;
}
//③ 在Adapter中實現(xiàn)3個方法
@Override
public void onBindViewHolder(VH holder, int position) {
holder.title.setText(mDatas.get(position));
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//item 點擊事件
}
});
}
@Override
public int getItemCount() {
return mDatas.size();
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
//LayoutInflater.from指定寫法
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_1, parent, false);
return new VH(v);
}
}
4. 設(shè)置RecyclerView
創(chuàng)建完Adapter肴掷,接著對RecyclerView進行設(shè)置,一般來說背传,需要為RecyclerView進行四大設(shè)置呆瞻,也就是后文說的四大組成:
- Layout Manager(必選)
- Adapter(必選)
- Item Decoration(可選,默認(rèn)為空)
- Item Animator(可選径玖,默認(rèn)為DefaultItemAnimator)
如果要實現(xiàn)ListView的效果栋烤,只需要設(shè)置Adapter和Layout Manager,如下:
List<String> data = initData();
RecyclerView rv = (RecyclerView) findViewById(R.id.rv);
//Layout Manager必選
rv.setLayoutManager(new LinearLayoutManager(this));
//Adapter 必選
rv.setAdapter(new NormalAdapter(data));
5. 四大組成
RecyclerView的四大組成是:
- Layout Manager:Item的布局挺狰。
- Adapter:為Item提供數(shù)據(jù)明郭。
- Item Decoration:Item之間的Divider。
- Item Animator:添加丰泊、刪除Item動畫薯定。
三、Layout Manager布局管理器
1. 布局管理器
在最開始就提到瞳购,RecyclerView 能夠支持各種各樣的布局效果话侄,這是 ListView 所不具有的功能,那么這個功能如何實現(xiàn)的呢学赛?其核心關(guān)鍵在于 RecyclerView.LayoutManager類中年堆。從前面的基礎(chǔ)使用可以看到,RecyclerView 在使用過程中要比 ListView 多一個 setLayoutManager 步驟盏浇,這個 LayoutManager 就是用于控制我們 RecyclerView 最終的展示效果的变丧。
LayoutManager負(fù)責(zé)RecyclerView的布局,其中包含了Item View的獲取與回收绢掰。
RecyclerView提供了三種布局管理器:
- LinerLayoutManager以垂直或者水平列表方式展示Item
- GridLayoutManager以網(wǎng)格方式展示Item
- StaggeredGridLayoutManager以瀑布流方式展示Item
如果你想用 RecyclerView 來實現(xiàn)自己自定義效果痒蓬,則應(yīng)該去繼承實現(xiàn)自己的 LayoutManager童擎,并重寫相應(yīng)的方法,而不應(yīng)該想著去改寫 RecyclerView攻晒。
2. LayoutManager 常見 API
關(guān)于 LayoutManager 的使用有下面一些常見的 API(有些在 LayoutManager 實現(xiàn)的子類中)
canScrollHorizontally();//能否橫向滾動
canScrollVertically();//能否縱向滾動
scrollToPosition(int position);//滾動到指定位置
setOrientation(int orientation);//設(shè)置滾動的方向
getOrientation();//獲取滾動方向
findViewByPosition(int position);//獲取指定位置的Item View
findFirstCompletelyVisibleItemPosition();//獲取第一個完全可見的Item位置
findFirstVisibleItemPosition();//獲取第一個可見Item的位置
findLastCompletelyVisibleItemPosition();//獲取最后一個完全可見的Item位置
findLastVisibleItemPosition();//獲取最后一個可見Item的位置
3. 局部刷新閃屏問題解決
對于RecyclerView的Item Animator顾复,有一個常見的坑就是“閃屏問題”。
這個問題的描述是:當(dāng)Item視圖中有圖片和文字鲁捏,當(dāng)更新文字并調(diào)用notifyItemChanged()時芯砸,文字改變的同時圖片會閃一下。這個問題的原因是當(dāng)調(diào)用notifyItemChanged()時给梅,會調(diào)用DefaultItemAnimator的animateChangeImpl()執(zhí)行change動畫乙嘀,該動畫會使得Item的透明度從0變?yōu)?,從而造成閃屏破喻。
解決辦法很簡單:
在rv.setAdapter()之前調(diào)用((SimpleItemAnimator)rv.getItemAnimator()).setSupportsChangeAnimations(false)禁用change動畫虎谢。
4.點擊事件
RecyclerView并沒有像ListView一樣暴露出Item點擊事件或者長按事件處理的api,也就是說使用RecyclerView時候曹质,需要我們自己來實現(xiàn)Item的點擊和長按等事件的處理婴噩。
實現(xiàn)方法有很多:
- 可以監(jiān)聽RecyclerView的Touch事件然后判斷手勢做相應(yīng)的處理,
- 也可以通過在綁定ViewHolder的時候設(shè)置監(jiān)聽羽德,然后通過Apater回調(diào)出去
我們選擇第二種方法几莽,更加直觀和簡單。
看一下Adapter的完整代碼宅静。
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{
// 展示數(shù)據(jù)
private ArrayList<String> mData;
// 事件回調(diào)監(jiān)聽
private MyAdapter.OnItemClickListener onItemClickListener;
public MyAdapter(ArrayList<String> data) {
this.mData = data;
}
public void updateData(ArrayList<String> data) {
this.mData = data;
notifyDataSetChanged();
}
// 添加新的Item
public void addNewItem() {
if(mData == null) {
mData = new ArrayList<>();
}
mData.add(0, "new Item");
notifyItemInserted(0);
}
// 刪除Item
public void deleteItem() {
if(mData == null || mData.isEmpty()) {
return;
}
mData.remove(0);
notifyItemRemoved(0);
}
// ① 定義點擊回調(diào)接口
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
// ② 定義一個設(shè)置點擊監(jiān)聽器的方法
public void setOnItemClickListener(MyAdapter.OnItemClickListener listener) {
this.onItemClickListener = listener;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 實例化展示的view
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_item, parent, false);
// 實例化viewholder
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
// 綁定數(shù)據(jù)
holder.mTv.setText(mData.get(position));
//③ 對RecyclerView的每一個itemView設(shè)置點擊事件
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(final View v) {
if(onItemClickListener != null) {
int pos = holder.getLayoutPosition();
onItemClickListener.onItemClick(holder.itemView, pos);
}
}
});
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
if(onItemClickListener != null) {
int pos = holder.getLayoutPosition();
onItemClickListener.onItemLongClick(holder.itemView, pos);
}
//表示此事件已經(jīng)消費章蚣,不會觸發(fā)單擊事件
return true;
}
});
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView mTv;
public ViewHolder(View itemView) {
super(itemView);
mTv = (TextView) itemView.findViewById(R.id.item_tv);
}
}
}
設(shè)置Adapter的事件監(jiān)聽。
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MDRvActivity.this,"click " + position + " item", Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MDRvActivity.this,"long click " + position + " item", Toast.LENGTH_SHORT).show();
}
});
5.網(wǎng)格樣式
RecyclerView展示的樣式由布局管理器LayoutManager來控制姨夹。
網(wǎng)格樣式的管理器是GridLayoutManager纤垂,看一下它最常用的兩個構(gòu)造函數(shù)以及參數(shù)含義。
-
GridLayoutManager(Context context, int spanCount)
- spanCount磷账,每列或者每行的item個數(shù)峭沦,設(shè)置為1,就是列表樣式
- 該構(gòu)造函數(shù)默認(rèn)是豎直方向的網(wǎng)格樣式
-
GridLayoutManager(Context context, int spanCount, int orientation,boolean reverseLayout)
- spanCount逃糟,每列或者每行的item個數(shù)吼鱼,設(shè)置為1,就是列表樣式
- 網(wǎng)格樣式的方向绰咽,水平(OrientationHelper.HORIZONTAL)或者豎直(OrientationHelper.VERTICAL)
- reverseLayout菇肃,是否逆向,true:布局逆向展示取募,false:布局正向顯示
// 豎直方向的網(wǎng)格樣式琐谤,每行四個Item
mLayoutManager = new GridLayoutManager(this, 4, OrientationHelper.VERTICAL, false);
mRecyclerView.setLayoutManager(mLayoutManager);
運行效果:
6.瀑布流樣式
RecyclerView的瀑布流布局管理器是StaggeredGridLayoutManager,它最常用的構(gòu)造函數(shù)就一個矛辕,StaggeredGridLayoutManager(int spanCount, int orientation)笑跛,spanCount代表每行或每列的Item個數(shù)付魔,orientation代表列表的方向聊品,豎直或者水平飞蹂。
看在代碼中的使用。
// 初始化布局管理器
mLayoutManager = new StaggeredGridLayoutManager(2, OrientationHelper.VERTICAL);
// 設(shè)置布局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
// 設(shè)置adapter
mRecyclerView.setAdapter(mAdapter);
// 設(shè)置間隔樣式
mRecyclerView.addItemDecoration(new MDStaggeredRvDividerDecotation(this));
要實現(xiàn)瀑布流效果(僅討論豎直方向的瀑布流樣式)翻屈,每一個Item的高度要有所差別陈哑,如果所有的item的高度相同,就和網(wǎng)格樣式是一樣的展示效果伸眶。示例中就實現(xiàn)兩中不同高度的Item惊窖,一個高度為80dp,一個高度為100dp。
view_rv_staggered_item.xml布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="80dp">
<TextView
android:id="@+id/item_tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:text="item"/>
</LinearLayout>
view_rv_staggered_item_two.xml布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="100dp">
<TextView
android:id="@+id/item_tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:text="item"/>
</LinearLayout>
Item不同的布局是在Adapter里面綁定的厘贼,看一下Adapter的實現(xiàn)界酒。
public class MDStaggeredRvAdapter extends RecyclerView.Adapter<MDStaggeredRvAdapter.ViewHolder> {
// 展示數(shù)據(jù)
private ArrayList<String> mData;
public MDStaggeredRvAdapter(ArrayList<String> data) {
this.mData = data;
}
public void updateData(ArrayList<String> data) {
this.mData = data;
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
// 瀑布流樣式外部設(shè)置spanCount為2,在這列設(shè)置兩個不同的item type嘴秸,以區(qū)分不同的布局
return position % 2;
}
@Override
public MDStaggeredRvAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 實例化展示的view
View v;
if(viewType == 1) {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_staggered_item, parent, false);
} else {
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_staggered_item_two, parent, false);
}
// 實例化viewholder
ViewHolder viewHolder = new ViewHolder(v);
return viewHolder;
}
@Override
public void onBindViewHolder(MDStaggeredRvAdapter.ViewHolder holder, int position) {
// 綁定數(shù)據(jù)
holder.mTv.setText(mData.get(position));
}
@Override
public int getItemCount() {
return mData == null ? 0 : mData.size();
}
public static class ViewHolder extends RecyclerView.ViewHolder {
TextView mTv;
public ViewHolder(View itemView) {
super(itemView);
mTv = (TextView) itemView.findViewById(R.id.item_tv);
}
}
}
四毁欣、總結(jié):
- 水平列表展示,設(shè)置LayoutManager的方向性
- 豎直列表展示岳掐,設(shè)置LayoutManager的方向性
- 自定義間隔凭疮,RecyclerView.addItemDecoration()
- Item添加和刪除動畫,RecyclerView.setItemAnimator()
- 網(wǎng)格樣式的布局管理器GridLayoutManager的spanCount設(shè)置為1,就是列表樣式
- 瀑布流樣式如果Item的布局文件是等高串述,豎直方向执解,就是豎直方向的網(wǎng)格樣式;如果Item是等寬纲酗,水平方向衰腌,那就是水平方向的網(wǎng)絡(luò)樣式
- 如果瀑布流樣式的布局管理器StaggeredGridLayoutManager的spanCount設(shè)置為1,豎直方向觅赊,是豎直方向的列表桶唐;水平方向,就是水平方向的列表
RecyclerView實現(xiàn)局部刷新
RecyclerView提供了notifyItemInserted(),notifyItemRemoved(),notifyItemChanged()等API更新單個或某個范圍的Item視圖茉兰。
gitHub 地址:https://github.com/lyyRunning/RecyclerViewDemo
作者:Rtia
鏈接:http://www.reibang.com/p/4f9591291365