RecyclerView常用功能全面總結
簡介
RecyclerView可以說是做Android應用開發使用最廣的幾個控制元件之一。它是在Android 5.0版本引入進來的,位於support-v7包中,可以通過在build.gradle中新增如下程式碼將它引入到專案中:
implementation 'com.android.support:recyclerview-v7:27.1.1'
和ListView的比較
在RecyclerView出現之前Android中的複用列表大多通過ListView實現的,RecyclerView並不會完全替代ListView,至少現在來看ListView並沒有被廢棄掉。RecyclerView和ListView互有優勢。
ListView的優點:
- 可以直接通過addHeaderView(), addFooterView()新增頭檢視和尾檢視。
- 直接提供了setOnItemClickListener()和setOnItemLongClickListener()設定點選事件和長按事件。
- “android:divider”設定自定義分割線。
RecyclerView的優點
- 提供了多種LayoutManager,可輕鬆實現多種樣式的佈局
- 支援區域性重新整理
- 已經實現了View的複用,不需要類似if(convertView == null)的實現,而且回收機制更加完善
- 容易實現新增item、刪除item的動畫效果
- 容易實現拖拽、側滑刪除等功能
簡單使用
RecylerView的使用幾個必須的步驟如下:
- 建立Adapter,並設定給RecylerView
- 為RecylerView設定LayoutManager
這兩個是RecylerView使用的必須步驟,可以看出
RecylerView和ListView的使用方法相比只是多了一步設定LayoutManager。
通過程式碼來看Adapter的具體實現:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private Context context;
private List<String> mDatas;
public MyAdapter(Context context , List<String> mDatas){
this.context = context;
this.mDatas = mDatas;
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item_layout,parent,false));
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.textView.setText("第"+position);
}
@Override
public int getItemCount() {
return mDatas.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder{
private TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textview);
}
}
}
將Adapter和LayoutManager設定給RecylerView:
myAdapter = new MyAdapter(this,datas);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
mRl.setLayoutManager(linearLayoutManager);
mRl.setAdapter(myAdapter);
這樣一個最基本的RecyclerView就實現了。
GridLayoutManager,StaggeredGridLayoutManager
在前面基本使用的程式碼中,給RecyclerView設定的是LinearLayoutManager,其實RecyclerView還提供了另外兩種佈局管理器,這就是GridLayoutManager和StaggeredGridLayoutManager,通過它們可以很簡單的分別實現表格佈局和瀑布流佈局。
通過GridLayoutManager可以讓RecyclerVie實現GridView的效果。GridLayoutManager的使用方式如下:
//4可以理解為一行顯示4列
GridLayoutManager linearLayoutManager = new GridLayoutManager(this,4);
mRl.setLayoutManager(linearLayoutManager);
通過StaggeredGridLayoutManager可以讓RecyclerVie實現瀑布流的效果。StaggeredGridLayoutManager的使用方式如下:
StaggeredGridLayoutManager linearLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
當然也可以根據需求自定義LayoutManager.通過原始碼就可以可以看到GridLayoutManager其實也是繼承了LinearLayoutManager,相當於自定義一個LinearLayoutManager,把一行一列,變為一行多列。
ItemDecoration
ItemDecoration其實翻譯過來就是item裝飾品,所以它不僅僅是用來繪製分割線,它可以繪製出各式各樣的的itemview的裝飾介面。只是由於RecyclerView中並沒有提供類似android:divider的方法來實現分割線,所以往往通過ItemDecoration來實現各種分割線效果。
ItemDecoration是一個抽象類,主要有下面3個方法:
//用於撐開整個itemview
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
//用於繪製具體的分隔線形狀,在itemview前面繪製,有可能被itemview覆蓋
public void onDraw(Canvas c, RecyclerView parent, State state)
//它是在itemview之後繪製,可以覆蓋itemview的內容
public void onDrawOver(Canvas c, RecyclerView parent, State state)
通過程式碼來實現一條綠色的分割線:
public class MyItemDecoration extends RecyclerView.ItemDecoration{
private int mDiverHeight;
private Paint mPaint;
public MyItemDecoration(){
mPaint = new Paint();
mPaint.setColor(Color.GREEN);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View view = parent.getChildAt(i);
if (parent.getChildAdapterPosition(view) !=0){
float top = view.getTop()-mDiverHeight;
float left = parent.getPaddingLeft();
float right = parent.getWidth()-parent.getPaddingRight();
float bottom = view.getTop();
c.drawRect(left,top,right,bottom,mPaint);
}
}
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
//
// int childCount = parent.getChildCount();
//
// for (int i = 0; i < childCount; i++) {
// View view = parent.getChildAt(i);
//
// if (parent.getChildAdapterPosition(view) !=0){
// float top = view.getTop()-mDiverHeight;
// float left = parent.getPaddingLeft();
// float right = parent.getWidth()-parent.getPaddingRight();
// float bottom = view.getTop();
//
// c.drawRect(left,top,right,bottom,mPaint);
// }
// }
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (parent.getChildLayoutPosition(view) != 0){
outRect.top = 10;
mDiverHeight = 40;
}
}
}
分析一下上面的程式碼,
1. 首先在getItemOffsets中利用 outRect.top在itemview的頂部撐開了10px的高度,將mDiverHeight的值設定為40px.
2. onDraw方法中具體繪製分隔線樣式
3. onDrawOver方法中同樣的程式碼繪製分割線
分別看下在不同方法中繪製出來的效果:
圖一,雖然mDiverHeight的高度設定為40px,但並沒有顯示那麼高,因為在getItemOffsets方法中只將itemview撐開了10px,進一步驗證了onDraw繪製的分隔線有可能被itemview覆蓋。
圖二,mDiverHeight的高度設定為40px,實際顯示也是40px,雖然在getItemOffsets方法中只將itemview撐開了10px,但是分隔線覆蓋掉了itemview部分介面,也驗證了onDrawOver繪製的分隔線會覆蓋itemview。
總結,在熟悉了onDraw和onDrawOver方法之後可以利用它們繪製出多種多樣的itemview裝飾介面。
ItemAnimator
通過ItemAnimator可以設定Item新增、刪除、更新的動畫,即使沒有設定它也會看到動畫,因為RecyclerView設定了預設動畫DefaultItemAnimator,通過程式碼來看下DefaultItemAnimator的效果:
DefaultItemAnimator defaultItemAnimator = new DefaultItemAnimator();
defaultItemAnimator.setAddDuration(1000);//設定時間長一點,容易看出效果
mRl.setItemAnimator(defaultItemAnimator);
需要注意的是這裡改變資料重新整理並不是呼叫的notifyDataSetChanged()方法,新增時呼叫notifyItemInserted(1),刪除時呼叫notifyItemRemoved(1)。
從效果可以看出預設動畫的效果是:
item新增時:漸變色由0-1顯現
item刪除時:漸變色由1-0消失
只有DefaultItemAnimator往往不能滿足需求,這時就需要自定義ItemAnimator,雖然動畫效果需要自定義,但是基本都是在add和remove時展示,所以只需要在DefaultItemAnimator的基礎上做修改就行了,下面實現一個在預設動畫的基礎上再增加一個平移動畫。Copy DefaultItemAnimator全部程式碼至MyItemAnimator,只需要修改新增部分:
MyItemAnimator.java
@Override
public boolean animateAdd(final RecyclerView.ViewHolder holder) {
resetAnimation(holder);
holder.itemView.setAlpha(0);
mPendingAdditions.add(holder);
//新增平移動畫,預設將itemview左移自身寬度
ViewCompat.setTranslationX(holder.itemView,-holder.itemView.getWidth());
mPendingAdditions.add(holder);
return true;
}
void animateAddImpl(final RecyclerView.ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimator animation = view.animate();
mAddAnimations.add(holder);
//新增translationX(0),將itemview平移出來
animation.alpha(1).translationX(0).setDuration(getAddDuration())
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchAddStarting(holder);
}
@Override
public void onAnimationCancel(Animator animator) {
ViewCompat.setAlpha(view, 1);
}
@Override
public void onAnimationEnd(Animator animator) {
animation.setListener(null);
dispatchAddFinished(holder);
mAddAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
效果如下:
一個簡單的自定義ItemAnimator就這樣實現了。Item側滑和拖拽
RecyclerView的item側滑功能主要是通過ItemTouchHelper實現的,它是一個支援RecyclerView滑動刪除和拖拽的實用工具類,使用也很簡單:
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mCallBack);
itemTouchHelper.attachToRecyclerView(mRl);
所以只需要關注這裡的mCallBack,看下它的實現:
public ItemTouchHelper.Callback mCallBack = new ItemTouchHelper.Callback() {
/**
* 指定可以拖拽(drag)和滑動(swipe)的方向
* @param recyclerView
* @param viewHolder
* @return
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
return 0;
}
/**
* 拖拽回撥
* @param recyclerView
* @param viewHolder
* @param target
* @return
*/
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}
/**
* 滑動回撥
* @param viewHolder
* @param direction
*/
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
}
};
但是在實際使用的時候更多的是通過SimpleCallback,它是繼承至ItemTouchHelper.Callback,通過程式碼來實現一個簡單的帶有側滑和拖拽的效果:
ItemTouchHelper.SimpleCallback mCallback =
new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP |
ItemTouchHelper.DOWN | ItemTouchHelper.LEFT |ItemTouchHelper.RIGHT,
ItemTouchHelper.START | ItemTouchHelper.END) {
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();//當前ViewHolder的position
int toPosition = target.getAdapterPosition();//目標ViewHolder的position
//交換位置
if (fromPosition < toPosition) {
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(datas, i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
Collections.swap(datas, i, i - 1);
}
}
myAdapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
datas.remove(position);//刪除資料
myAdapter.notifyItemRemoved(position);
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
//滑動時改變Item的透明度
final float alpha = 1 - Math.abs(dX) / (float) viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(alpha);
viewHolder.itemView.setTranslationX(dX);
}
}
};
然後看下效果圖:
可以看到最基本的拖拽和側滑都已經實現了。多種型別佈局
RecyclerView的常用功能裡面還有就是多種狀態佈局,也就是用一個RecyclerView實現有多種狀態的佈局,比如給RecyclerView增加一個Header和Footer
然後中間是常規資料,主要通過Adapter的getItemViewType方法來區分不同的佈局。修改Adapter的程式碼如下:
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context context;
private List<String> mDatas;
public static final int ITME_TYPE_HEADER = 1;
public static final int ITME_TYPE_CONTENT = 2;
public static final int ITME_TYPE_BOTTOM = 3;
private int mHeaderCount = 1;
private int mBottmoCount = 1;
public MyAdapter(Context context , List<String> mDatas){
this.context = context;
this.mDatas = mDatas;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType){
case ITME_TYPE_HEADER:
return new HeadViewHolder(LayoutInflater.from(context).inflate(R.layout.item_header,parent,false));
case ITME_TYPE_CONTENT:
return new MyViewHolder(LayoutInflater.from(context).inflate(R.layout.item_layout,parent,false));
case ITME_TYPE_BOTTOM:
return new BottomViewHolder(LayoutInflater.from(context).inflate(R.layout.item_header,parent,false));
}
return null;
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof HeadViewHolder){
((HeadViewHolder) holder).textView_header.setText("我是頭");
}else if (holder instanceof MyViewHolder){
//注意這裡要減去頭的數量
((MyViewHolder) holder).textView.setText("第"+(position-mHeaderCount));
}else if (holder instanceof BottomViewHolder){
((BottomViewHolder) holder).textView_bottom.setText("我是尾");
}
}
/**
* 要加上頭尾
* @return
*/
@Override
public int getItemCount() {
return mDatas.size()+mHeaderCount+mBottmoCount;
}
/**
* 根據position來區分佈局型別
* @param position
* @return
*/
@Override
public int getItemViewType(int position) {
if (position<mHeaderCount){
return ITME_TYPE_HEADER;
}else if(position>= mHeaderCount+mDatas.size()){
return ITME_TYPE_BOTTOM;
}else{
return ITME_TYPE_CONTENT;
}
}
public class MyViewHolder extends RecyclerView.ViewHolder{
private TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.textview);
}
}
//頭部ViewHolder
public static class HeadViewHolder extends RecyclerView.ViewHolder {
private TextView textView_header;
public HeadViewHolder(View itemView) {
super(itemView);
textView_header = itemView.findViewById(R.id.textView1);
}
}
//尾部ViewHodler
public static class BottomViewHolder extends RecyclerView.ViewHolder {
private TextView textView_bottom;
public BottomViewHolder(View itemView) {
super(itemView);
textView_bottom = itemView.findViewById(R.id.textView1);
}
}
}
效果如圖:
總結
RecyclerView真的很強大,很好用。