很早之前就聽說過RecyclerView這個組件了,但一直很忙沒時間學習。趁著周末,就花了一天時間來學習RecyclerView。 準備工作 在Android Studio里新建一個Android項目,添加以下工具: 前兩個工具就不說了,基本每個Android項目都會用到,第三個就是使用Recycl ...
很早之前就聽說過RecyclerView這個組件了,但一直很忙沒時間學習。趁著周末,就花了一天時間來學習RecyclerView。
準備工作
在Android Studio里新建一個Android項目,添加以下工具:
compile 'com.android.support:support-v4:25.2.0' compile 'com.android.support:appcompat-v7:25.2.0' compile 'com.android.support:recyclerview-v7:25.2.0' compile 'com.github.bumptech.glide:glide:3.7.0'
前兩個工具就不說了,基本每個Android項目都會用到,第三個就是使用RecyclerView需要添加的工具了,而最後一個工具是Google推薦使用的一個開源圖像載入工具,它使得載入圖像變得更為流暢,並且使用起來容易上手。
在drawable目錄下放入一些圖像,並將這些圖像名命名為"image[a-zA-Z0-9]*"的形式(因為在程式里會用反射來查找所有以"image"開頭的圖像資源)。
在drawable目錄下放入"add.png"和"remove.png"等用於頂部工具欄的圖像資源。
基本用法
使用RecyclerView,可以依照如下步驟:
1. 在資源文件中定義一個RecyclerView組件;
2. 在Activity中獲取定義的RecyclerView組件,變數名就簡單叫作view;
3. 為view設置LayoutManager(佈局);
4. 為view設置Adapter(適配器);
5. 為view添加ItemDecoration(分隔符,可選);
6. 為view設置ItemAnimator(動畫,可選)。
下麵在資源文件中定義了一個RecyclerView組件:
1 <android.support.v7.widget.RecyclerView 2 android:layout_width="match_parent" 3 android:layout_height="0dp" 4 android:layout_weight="1" 5 android:id="@+id/vert_view" />
然後就可以在Activity中取得這個RecyclerView組件:
1 RecyclerView view=(RecyclerView) findViewById(R.id.vert_view);
至於其他的細節將在下文介紹。
Adapter
用於RecyclerView的適配器需要繼承自RecyclerView.Adapter<T>類,並重寫其中的 T onCreateViewHolder(final ViewGroup parent, int viewType) 、 void onBindViewHolder(T holder, final int position) 和 int getItemCount() 方法。下麵是一個自定義的Adapter:
1 public class ViewAdapter extends RecyclerView.Adapter<ViewAdapter.ViewHolder> { 2 private Context context; 3 private List<Animal> datas; 4 private int layoutResId; 5 private int imageViewId; 6 private int textViewId; 7 private boolean isCenterCrop; 8 9 public ViewAdapter(Context c, List<Animal> list, int lri, int ivi, int tvi, boolean centerCrop) { 10 context=c; 11 datas=list; 12 layoutResId=lri; 13 imageViewId=ivi; 14 textViewId=tvi; 15 isCenterCrop=centerCrop; 16 } 17 18 @Override 19 public ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) { 20 View view= LayoutInflater.from(context).inflate(layoutResId, parent, false); 21 return new ViewHolder(view, imageViewId, textViewId); 22 } 23 24 @Override 25 public void onBindViewHolder(ViewHolder holder, final int position) { 26 final Animal animal=datas.get(position); 27 if(isCenterCrop) 28 Glide.with(context).load(animal.getImgResId()).centerCrop().into(holder.imageView); 29 else 30 Glide.with(context).load(animal.getImgResId()).into(holder.imageView); 31 holder.textView.setText(animal.getDescription()); 32 holder.view.setOnClickListener(new View.OnClickListener() { 33 @Override 34 public void onClick(View v) { 35 Intent intent=new Intent(context, DetailActivity.class); 36 intent.putExtra("IMG_RES_ID", animal.getImgResId()); 37 intent.putExtra("DESCRIPTION", animal.getDescription()); 38 context.startActivity(intent); 39 } 40 }); 41 holder.view.setOnLongClickListener(new View.OnLongClickListener() { 42 @Override 43 public boolean onLongClick(View v) { 44 Toast.makeText(context, "第"+position+"個元素", Toast.LENGTH_SHORT).show(); 45 return true; 46 } 47 }); 48 } 49 50 @Override 51 public int getItemCount() { 52 return datas.size(); 53 } 54 55 public void addItem(int position) { 56 datas.add(position, ResourceUtils.getRandomAnimal()); 57 notifyItemInserted(position); 58 } 59 60 public void removeItem(int position) { 61 if(position>=0 && position<datas.size()) { 62 datas.remove(position); 63 notifyItemRemoved(position); 64 } 65 } 66 67 static class ViewHolder extends RecyclerView.ViewHolder { 68 View view; 69 ImageView imageView; 70 TextView textView; 71 72 public ViewHolder(View itemView, int ivi, int tvi) { 73 super(itemView); 74 view=itemView; 75 imageView=(ImageView) itemView.findViewById(ivi); 76 textView=(TextView) itemView.findViewById(tvi); 77 } 78 } 79 }
註意:
- 其中的Animal類封裝了一張(動物)圖像的資源ID和一段描述(字元串),Animal對象將作為我們的RecyclerView中的元素;
- onCreateViewHolder方法負責創建一個ViewHolder,因此我們需要提供一個元素使用的資源文件ID,我們需要使用到的view(這裡包括一個ImageView和一個TextView)都可以通過這個ViewHolder得到;
- onBindViewHolder方法負責對某些view進行設置,這裡我們為ImageView設置了圖像,為TextView設置了文本,併為它們的父view註冊了點擊和長按監聽器;
- 這裡的addItem和removeItem方法是自定義方法,用於向/從RecyclerView中添加/移除一個元素(將在後面介紹),這裡可以先忽略;
- 這裡的ViewHolder是ViewAdapter.ViewHolder,它繼承自RecyclerView.ViewHolder,我們將需要使用到的view交給它管理。
定義好Adapter後,就可以為RecyclerView設置適配器了:
1 List<Animal> datas= ResourceUtils.getAnimals(); 2 adapter=new ViewAdapter(this, datas, R.layout.item_vertical, R.id.vert_image, R.id.vert_text, true); 3 view.setAdapter(adapter);
垂直佈局
要想讓RecyclerView使用和ListView一樣的佈局,只需要為RecyclerView設置一個線性佈局管理器就行了:
1 view.setLayoutManager(new LinearLayoutManager(this));
水平佈局
使用水平佈局只需設置LinearLayoutManager的方向即可:
1 LinearLayoutManager layoutManager=new LinearLayoutManager(this); 2 layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL); 3 view.setLayoutManager(layoutManager);
網格佈局
使用網格佈局需要藉助GridLayoutManager類:
1 GridLayoutManager manager=new GridLayoutManager(this, 3); 2 // manager.setOrientation(GridLayoutManager.HORIZONTAL); 3 view.setLayoutManager(manager);
創建GridLayoutManager時,第二個參數使用列數/行數。GridLayoutManager預設是上下滑動的,如果想要左右滑動,可以取消第二行的註釋。
瀑布流佈局
使用瀑布流佈局只需使用StaggeredGridLayoutManager即可:
1 view.setLayoutManager(new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL));
StaggeredGridLayoutManager的第一個參數指定列數/行數,第二個參數指定滑動方向,這裡設置成上下滑動。如果使用StaggeredGridLayoutManager時所有元素的大小一致,則效果和網格佈局一樣。
添加分割線
RecyclerView預設是不顯示分割線的,這點和ListView不同,但這也為更靈活的定製自己的分割線提供了機會。
要想定製自己的分割線,需要定義類繼承自RecyclerView.ItemDecoration,並實現如下方法:
- void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) :此方法負責繪製分割線;
- void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) :此方法和onDraw方法一樣,也是繪製分割線,不過繪製的時機是在繪製元素View完成後,通常只需要重寫onDraw和此方法中的一個即可;
- void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) :此方法計算分割線的尺寸,以方便計算元素View的偏移。
這裡以繪製垂直佈局的分割線為例,介紹如何定義自己的分割線,至於其他幾種佈局的分割線可以參考著來寫,文章最後也會提供所有源碼:
1 public class VerticalItemDecoration extends RecyclerView.ItemDecoration { 2 private Drawable divider; 3 private static final int[] ATTRS=new int[] { 4 android.R.attr.listDivider, 5 }; 6 7 public VerticalItemDecoration(Context c) { 8 TypedArray array=c.obtainStyledAttributes(ATTRS); 9 divider=array.getDrawable(0); 10 array.recycle(); 11 } 12 13 @Override 14 public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 15 super.onDraw(c, parent, state); 16 DecorationUtils.onVerticalDraw(c, parent, divider); 17 } 18 19 @Override 20 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { 21 super.getItemOffsets(outRect, view, parent, state); 22 int position=parent.getChildLayoutPosition(view); 23 int childCount=parent.getAdapter().getItemCount(); 24 if((position+1)<childCount) 25 outRect.set(0, 0, 0, divider.getIntrinsicHeight()); 26 } 27 }
1 public class DecorationUtils { 2 public static void onVerticalDraw(Canvas c, RecyclerView parent, Drawable divider) { 3 int left=parent.getPaddingLeft(); 4 int right=parent.getWidth()-parent.getPaddingRight(); 5 6 for(int i=0; i<parent.getChildCount()-1; i++) { 7 View child=parent.getChildAt(i); 8 RecyclerView.LayoutParams params=(RecyclerView.LayoutParams) child.getLayoutParams(); 9 int top=child.getBottom()+params.bottomMargin; 10 int bottom=top+divider.getIntrinsicHeight(); 11 divider.setBounds(left, top, right, bottom); 12 divider.draw(c); 13 } 14 } 15 // ... 16 }
這裡的分隔符樣式使用了Android提供的“listDivider”,你可以重定義listDivider屬性來修改分割線樣式:
1 <!-- Base application theme. --> 2 <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> 3 <!-- Customize your theme here. --> 4 <item name="colorPrimary">@color/colorPrimary</item> 5 <item name="colorPrimaryDark">@color/colorPrimaryDark</item> 6 <item name="colorAccent">@color/colorAccent</item> 7 <item name="android:listDivider">@drawable/divider</item> 8 </style>
添加動畫
你可能會好奇,RecyclerView為什麼需要添加動畫,不是就一個滑動嗎?難道是添加滑動的動畫?
考慮這種情況,當需要向/從RecyclerView中添加/移除元素時,如果一個元素突然插進來或者突然消失,用戶會是什麼感受(肯定覺得這APP是不是犯二了)。如果在添加/移除元素時,能有一個動畫顯示元素的出現/消失,那用戶體驗就更好了。所以,我們這裡是為元素添加動畫。
RecyclerView提供了一個預設動畫,只需要簡單的一行代碼:
1 view.setItemAnimator(new DefaultItemAnimator());
好了,現在我們已經為元素的出現/消失添加了動畫了。那麼問題又來了,我們怎麼添加/移除元素呢?
還記得我們上面介紹適配器時提供的ViewAdapter嗎?當時讓你暫時忽略了兩個方法——addItem和removeItem,這兩個方法就是用於添加/移除元素的。
我們現在來看下細節:
1 public void addItem(int position) { 2 datas.add(position, ResourceUtils.getRandomAnimal()); 3 notifyItemInserted(position); 4 } 5 6 public void removeItem(int position) { 7 if(position>=0 && position<datas.size()) { 8 datas.remove(position); 9 notifyItemRemoved(position); 10 } 11 }
註意到調用了notifyItemInserted和notifyItemRemoved方法,它們就是用於通知RecyclerView元素髮生改變的。
現在已經有了添加/移除元素的介面,下麵還需要在Activity中添加交互的動作。我們提供了一個Toolbar(用於代替ActionBar的一個組件),並提供了兩個item。用戶可以點擊這兩個item來添加/移除元素:
1 @Override 2 protected void onCreate(Bundle savedInstanceState) { 3 // ... 4 Toolbar toolbar=(Toolbar) findViewById(R.id.toolbar); 5 setSupportActionBar(toolbar); 6 // ... 7 } 8 9 @Override 10 public boolean onCreateOptionsMenu(Menu menu) { 11 getMenuInflater().inflate(R.menu.toolbar, menu); 12 return true; 13 } 14 15 @Override 16 public boolean onOptionsItemSelected(MenuItem item) { 17 switch (item.getItemId()) { 18 case R.id.add: 19 adapter.addItem(0); 20 view.smoothScrollToPosition(0); 21 break; 22 case R.id.remove: 23 adapter.removeItem(0); 24 break; 25 default:break; 26 } 27 return true; 28 } 29 }
好了,現在你就可以在添加/移除元素時看到動畫效果了。
源代碼
整個項目的源代碼已經上傳到GitHub:
https://github.com/jzyhywxz/RecyclerView