Android UI設計之RecyclerView
RecyclerView簡介
RecyclerView是繼ListView和GridView後Google又一力作,它不僅可以很方便的實現瀑布流效果,而且大幅度降低了檢視的耦合性,在設計上有很高的自由度。
本文主要分析RecyclerView的使用技巧以及優化。
使用前請自行新增依賴:
compile 'com.android.support:recyclerview-v7:23.3.0'
- 1
RecyclerView原理
RecyclerViewRecyclerView與ListView原理是類似的,都是僅僅維護少量的View並且可以展示大量的資料集,此外通過Google提供的方法可以快捷管理RecyclerView的風格樣式。
- LayoutManager控制每個Item的排列方式
- ItemDecoration控制每個Item的修飾
- ItemAnimator設定Item的增刪動畫
- Adapter為每個Item提供對應的資料
LayoutManager:用來確定每一個item如何進行排列擺放,何時展示和隱藏。回收或重用一個View的時候,LayoutManager會向介面卡請求新的資料來替換舊的資料,這種機制避免了建立過多的View和頻繁的呼叫findViewById方法(與ListView原理類似)。
目前SDK中提供了三種自帶的LayoutManager: LinearLayoutManager GridLayoutManager StaggeredGridLayoutManager(錯列網格佈局)
RecyclerView四步走
//設定佈局樣式mRecyclerView.setLayoutManager();//設定adaptermRecyclerView.setAdapter()//設定Item增加、移除動畫mRecyclerView.setItemAnimator();//新增分割線mRecyclerView.addItemDecoration();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
OK,基本就是這麼個流程,下面來逐步進行實踐
ItemDecoration
官方提供了分割線介面ItemDecoration,實現該方法可以自定義你所需要的分割線。
一個很好的重寫Demo,有愛自取,來源於翔哥文章 使用:
mRecyclerView.addItemDecoration(new DividerLinearItemDecoration(this, LinearLayout.HORIZONTAL));mRecyclerView.addItemDecoration(new DividerLinearItemDecoration(this, LinearLayout.VERTICAL));
- 1
- 2
分別新增橫線和豎線
線性佈局分割線
public class DividerLinearItemDecoration 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 DividerLinearItemDecoration(Context context, int orientation) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); setOrientation(orientation); } public void setOrientation(int orientation) { if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { throw new IllegalArgumentException("invalid orientation"); } mOrientation = orientation; } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { if (mOrientation == VERTICAL_LIST) { drawVertical(c, parent); } else { drawHorizontal(c, parent); } } public 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 = 0; i < childCount; i++) { final View child = parent.getChildAt(i); android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext()); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } public 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 = 0; i < childCount; i++) { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int left = child.getRight() + params.rightMargin; final int right = left + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if (mOrientation == VERTICAL_LIST) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } }}
- 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
自定義效果: 上面程式碼已經對分割線做了很好的繪製,注意到還都是用了系統的預設分割線android.R.attr.listDivider
,基於此我們可以很容易的去自定義的分割線的效果。
自定義流程: 在style檔案中新增如下程式碼:
<style name="AppTheme" parent="AppBaseTheme"> <item name="android:listDivider">@drawable/divider_bg</item> </style>
- 1
- 2
- 3
然後自己利用程式碼繪製一個drawable物件作為分割線底色即可。比如
<?xml version="1.0" encoding="utf-8"?><shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@color/colorAccent"/></shape>
- 1
- 2
- 3
- 4
- 5
setItemAnimator
Itme增刪動畫效果,官方提供了一個預設的動畫效果,直接新增即可,
recyclerView.setItemAnimator(new DefaultItemAnimator()); // 預設動畫
- 1
當然你也可以去自行定製。
嗯,自定義的終究難以滿足各位的需求,來一個我目前所知最全面的RecyclerView動畫庫,有愛自取,嗯。RecyclerView Animators
RecyclerView.Adapter
RecyclerView的介面卡預設要求必須實現系統提供的RecyclerView.ViewHolder
介面,
寫法:
public class AnimationAdapter extends RecyclerView.Adapter<AnimationAdapter.MyViewHolder>{ @Override public AnimationAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return null; } @Override public void onBindViewHolder(MyViewHolder holder, int position) { } @Override public int getItemCount() { return 0; } //請首先實現RecyclerView.ViewHolder public class MyViewHolder extends RecyclerView.ViewHolder{ public MyViewHolder(View itemView) { super(itemView); } }}
- 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
顯然,要求必須實現三個方法,在此之前需要先實現RecyclerView.ViewHolder
。
LayoutManager
RecyclerView的關鍵,決定佈局的樣式。 前面已經提到主要有三類佈局形式,線性佈局、網格佈局和瀑布流佈局,它們分別對應著LinearLayoutManager、GridLayoutManager、StaggeredGridLayoutManager。
通過這三中佈局管理我們可以很容易的動態調整介面設計,比如我們預設顯示一個線性佈局
mRecyclerView.setLayoutManger(new LinearLayoutManger(Context,LinearLayoutManger.VERTICAL,false);
- 1
當發生介面旋轉或其他需求需要顯示網格樣式佈局那麼可以直接重設LayoutManger為我們想要的佈局。
mRecyclerView.setLayoutManger(new GridLayoutManager(this,3,GridLayoutManager.VERTICAL,false));
- 1
- 2
新增和移除Item的實現
//新增Item public void addItem(int position) { Animation animation; animation = animationList.get((int)(Math.random()*9)); animationList.add(position, animation); //使總position+1 notifyItemInserted(position); } //移除Item public void delItem(int position) { animationList.remove(position); //使總position-1 notifyItemRemoved(position); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
實現RecyclerView的Item監聽
實現監聽主要有兩種方式:
- 在介面卡中建立回撥介面
- 覆寫
addOnItemTouchListener
方法
在介面卡中建立回撥介面
第一步:
//為Item建立監聽介面 public interface OnRecyclerViewItemClickListener { void onItemClick(View view , Data data); }
- 1
- 2
- 3
- 4
view和data分別對應當前Item的View檢視和資料。 第二步:
//例項化介面接收外部設定的listener private OnRecyclerViewItemClickListener mOnItemClickListener; public void setOnItemClickListener(OnRecyclerViewItemClickListener listener) { this.mOnItemClickListener = listener; }
- 1
- 2
- 3
- 4
- 5
- 6
第三步:在onBindViewHolder
中新增如下程式碼
// 如果設定了回撥,則設定點選事件 if (mOnItemClickLitener != null) { holder.itemView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { int position = holder.getLayoutPosition(); mOnItemClickLitener.onItemClick(holder.itemView, position); } }); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
其實,你也可以在onCreateViewHolder
為所有Item設定好監聽,但相比於這種方式,靈活度卻要低了一點。
so,這樣就為每個Item設定了回撥監聽,當然你完全可以為holder.itemView設定其他任何你所需要的觸控監聽。
覆寫addOnItemTouchListener
方法
大致看一下實現吧
mRecyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() { @Override public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { return false; } @Override public void onTouchEvent(RecyclerView rv, MotionEvent e) { } @Override public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } });
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
每個方法都對應著不同的用法,不懂得請速速參考郭霖或者鴻洋大神的部落格。懶人鏈接。
基於此,做了一個小Demo,效果如下: 線性佈局
網格佈局
瀑布流
圖片太大,請原諒用了三個圖。
關於監聽事件的測試(點選後刪除點選位置元素):
ps: - notifyItemChanged(int position):當Item資料改動時回撥onBindViewHolder即可更新資料。 - notifyItemRangeChanged(int positionStart, int itemCount):重新整理從positionStart開始itemCount數量的item - notifyItemInserted(int position):插入Item,(記得更新資料表) - notifyItemMoved(int fromPosition, int toPosition):從fromPosition移動到toPosition為止的時候可以使用這個方法重新整理 - notifyItemRangeInserted(int positionStart, int itemCount):批量新增。 - notifyItemRemoved(int position):第position個被刪除的時候重新整理,同樣會有動畫。 - notifyItemRangeRemoved(int positionStart, int itemCount):批量刪除。 - notifyDataSetChanged():更新資料表和Item位置資訊
OK,本文就寫到這裡吧,有疑問的朋友歡迎指出問題,一篇文章認認真真寫下來也真是蠻累的,如果對你有幫助,諸位道友大可多多支援,不甚感激。
關於原始碼:有愛自取