1. 概述
官方介紹,RecyclerView用于在有限的視窗展現大量的資料,其實早已經有了類似的控件,如ListView、GridView,那麼相比它們,RecyclerView有什麼樣優勢呢?
RecyclerView标準化了ViewHolder,而且異常的靈活,可以輕松實作ListView實作不了的樣式和功能,通過布局管理器LayoutManager可控制Item的布局方式,通過設定Item操作動畫自定義Item添加和删除的動畫,通過設定Item之間的間隔樣式,自定義間隔。
- 設定布局管理器以控制Item的布局方式,橫向、豎向以及瀑布流方式。
- 可設定Item操作的動畫(删除或者添加等)
- 可設定Item的間隔樣式(可繪制)
但是關于Item的點選和長按事件,需要使用者自己去實作。
在使用RecyclerView時候,必須指定一個擴充卡Adapter和一個布局管理器LayoutManager。擴充卡繼承
RecyclerView.Adapter
類,具體實作類似ListView的擴充卡,取決于資料資訊以及展示的UI。布局管理器用于确定RecyclerView中Item的展示方式以及決定何時複用已經不可見的Item,避免重複建立以及執行高成本的
findViewById()
方法。
來看一下用法。
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// 設定布局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
// 設定adapter
mRecyclerView.setAdapter(mAdapter);
// 設定Item添加和移除的動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
// 設定Item之間間隔樣式
mRecyclerView.addItemDecoration(mDividerItemDecoration);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
可以看見RecyclerView相比ListView會多出許多操作,這也是RecyclerView靈活的地方,它将許多動能暴露出來,使用者可以選擇性的自定義屬性以滿足需求。
RecyclerView提供了三種布局管理器:
- LinerLayoutManager 以垂直或者水準清單方式展示Item
- GridLayoutManager 以網格方式展示Item
- StaggeredGridLayoutManager 以瀑布流方式展示Item
2. 基本使用
在build.gradle檔案中引入該類。
compile 'com.android.support:recyclerview-v7:23.4.0'
- 1
- 1
Activity代碼
public class MDRvActivity extends MDBaseActivity {
private RecyclerView mRecyclerView;
private RecyclerView.Adapter mAdapter;
private RecyclerView.LayoutManager mLayoutManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_rv);
initData();
initView();
}
private void initData() {
mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mAdapter = new MyAdapter(getData());
}
private void initView() {
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
// 設定布局管理器
mRecyclerView.setLayoutManager(mLayoutManager);
// 設定adapter
mRecyclerView.setAdapter(mAdapter);
}
private ArrayList<String> getData() {
ArrayList<String> data = new ArrayList<>();
String temp = " item";
for(int i = ; i < ; i++) {
data.add(i + temp);
}
return data;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
Activity布局檔案activity_rv.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
RecyclerView擴充卡Adapter代碼
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{
private ArrayList<String> mData;
public MyAdapter(ArrayList<String> data) {
this.mData = data;
}
public void updateData(ArrayList<String> data) {
this.mData = data;
notifyDataSetChanged();
}
@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(ViewHolder holder, int position) {
// 綁定資料
holder.mTv.setText(mData.get(position));
}
@Override
public int getItemCount() {
return mData == null ? : 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);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
Item的布局檔案view_rv_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="@dimen/md_common_view_height">
<TextView
android:id="@+id/item_tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:text="item"/>
</LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
運作結果如下:
圖-1 RecyclerView無間隔
可以看見展示效果和ListView基本上無差别,但是Item之間并沒有分割線,在xml去找divider屬性的時候,發現RecyclerView沒有divider屬性,當然也可以在Item布局中加上分割線,但是這樣做并不是很優雅。前文說過,RecyclerView可是支援自定義間隔樣式的。通過
mRecyclerView.addItemDecoration()
來設定我們定義好的間隔樣式。
3. 間隔樣式
自定義間隔樣式需要繼承
RecyclerView.ItemDecoration
類,該類是個抽象類,主要有三個方法。
- onDraw(Canvas c, RecyclerView parent, State state),在Item繪制之前被調用,該方法主要用于繪制間隔樣式
- onDrawOver(Canvas c, RecyclerView parent, State state),在Item繪制之前被調用,該方法主要用于繪制間隔樣式
- getItemOffsets(Rect outRect, View view, RecyclerView parent, State state),設定item的偏移量,偏移的部分用于填充間隔樣式,在RecyclerView的
中會調用該方法onMesure()
onDraw()
和
onDrawOver()
這兩個方法都是用于繪制間隔樣式,我們隻需要複寫其中一個方法即可。直接來看一下自定義的間隔樣式的實作吧,參考官方執行個體。
public class MyDividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
/**
* 用于繪制間隔樣式
*/
private Drawable mDivider;
/**
* 清單的方向,水準/豎直
*/
private int mOrientation;
public MyDividerItemDecoration(Context context, int orientation) {
// 擷取預設主題的屬性
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable();
a.recycle();
setOrientation(orientation);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
// 繪制間隔
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(, , , mDivider.getIntrinsicHeight());
} else {
outRect.set(, , mDivider.getIntrinsicWidth(), );
}
}
private void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
/**
* 繪制間隔
*/
private void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = ; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin +
Math.round(ViewCompat.getTranslationY(child));
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
/**
* 繪制間隔
*/
private void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = ; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int left = child.getRight() + params.rightMargin +
Math.round(ViewCompat.getTranslationX(child));
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
然後在代碼中設定RecyclerView的間隔樣式。
mRecyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.VERTICAL));
- 1
- 1
來看一下展示效果。
圖-2 RecyclerView有間隔
既然RecyclerView還支援水準清單,簡單改一下屬性,看看水準清單的顯示效果。
修改Item的布局檔案view_rv_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="@dimen/md_common_view_width"
android:layout_height="match_parent">
<TextView
android:id="@+id/item_tv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:text="item"/>
</LinearLayout>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
修改LayoutManager的初始化和間隔樣式初始化。
mLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
// 設定Item之間間隔樣式
mRecyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.HORIZONTAL));
- 1
- 2
- 3
- 1
- 2
- 3
看一下水準清單效果。
圖-3 RecyclerView水準清單
3. 動畫設定
前面說過,RecyclerView可以設定清單中Item删除和添加的動畫,在v7包中給我們提供了一種預設的Item删除和添加的動畫,如果沒有特殊的需求,預設使用這個動畫即可。
// 設定Item添加和移除的動畫
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
- 1
- 2
- 1
- 2
下面就添加一下删除和添加Item的動作。在Adapter裡面添加方法。
public void addNewItem() {
if(mData == null) {
mData = new ArrayList<>();
}
mData.add(, "new Item");
notifyItemInserted();
}
public void deleteItem() {
if(mData == null || mData.isEmpty()) {
return;
}
mData.remove();
notifyItemRemoved();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
添加事件的處理。
public void onClick(View v) {
int id = v.getId();
if(id == R.id.rv_add_item_btn) {
mAdapter.addNewItem();
// 由于Adapter内部是直接在首個Item位置做增加操作,增加完畢後清單移動到首個Item位置
mLayoutManager.scrollToPosition();
} else if(id == R.id.rv_del_item_btn){
mAdapter.deleteItem();
// 由于Adapter内部是直接在首個Item位置做删除操作,删除完畢後清單移動到首個Item位置
mLayoutManager.scrollToPosition();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
準備工作完畢後,來看一下運作的效果。
圖-4 RecyclerView動畫
4. 點選事件
RecyclerView并沒有像ListView一樣暴露出Item點選事件或者長按事件處理的api,也就是說使用RecyclerView時候,需要我們自己來實作Item的點選和長按等事件的處理。實作方法有很多,可以監聽RecyclerView的Touch事件然後判斷手勢做相應的處理,也可以通過在綁定ViewHolder的時候設定監聽,然後通過Apater回調出去,我們選擇第二種方法,更加直覺和簡單。
看一下Adapter的完整代碼。
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>{
/**
* 展示資料
*/
private ArrayList<String> mData;
/**
* 事件回調監聽
*/
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(, "new Item");
notifyItemInserted();
}
/**
* 删除Item
*/
public void deleteItem() {
if(mData == null || mData.isEmpty()) {
return;
}
mData.remove();
notifyItemRemoved();
}
/**
* 設定回調監聽
*
* @param listener
*/
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) {
// 綁定資料
holder.mTv.setText(mData.get(position));
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);
}
//表示此事件已經消費,不會觸發單擊事件
return true;
}
});
}
@Override
public int getItemCount() {
return mData == null ? : 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);
}
}
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
設定Adapter的事件監聽。
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();
}
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
最後的實作效果。
圖-5 RecyclerView點選效果
5. 總結
可以看見相比于ListView,RecyclerView非常靈活,但其實這篇文章隻是介紹了RecyclerView的基本使用,并沒有深入,比如像網格展示和瀑布流展示都沒有介紹,而且這篇文章為了詳細的介紹使用方法,貼了大量的源代碼,導緻篇幅過長,不得以要将RecyclerView的使用分好幾篇來介紹。就目前而言,我們已經知道RecyclerView的一些功能如下。
- 水準清單展示,設定LayoutManager的方向性
- 豎直清單展示,設定LayoutManager的方向性
- 自定義間隔,RecyclerView.addItemDecoration()
- Item添加和删除動畫,RecyclerView.setItemAnimator()
是以在項目中如果再遇見清單類的布局,就可以優先考慮使用RecyclerView,更靈活更快捷的使用方式會給編碼帶來不一樣的體驗。如果你以為這些就是RecyclerView相比ListView/GridView優勢的話,那就大錯特錯了,關于RecyclerView還有更多靈活的功能,在後面文章會慢慢介紹。
附上Demo位址:https://github.com/Kyogirante/MaterialDesignDemo
在下一篇會主要介紹RecyclerView的其他兩種展示方式,網格和瀑布流。
原文出處:http://blog.csdn.net/xiaohanluo/article/details/52191868