Android控件RecyclerView的基本用法


Android控件RecyclerView的基本用法

 
github:

前言:雖然在日常開發中已經多次接觸過RecycleView,但也只是用到其最基本的功能,並沒有深入研究其他內容。接下來將抽出時間去了解RecycleView的相關內容,同時在博客中進行記錄,以此加深印象。這篇文章主要是介紹RecycleView的使用方法。

一、RecyclerView是什么

       RecycleView是Android5.0后谷歌推出的一個用於在有限的窗口中展示大量數據集的控件,位於support-v7包中。它可以實現與ListView和GridView一樣的效果,提供了一種插拔式的體驗,高度的解耦,異常的靈活,只需設置其提供的不同的LayoutManager,ItemAnimator和ItemDecoration,就能實現不同的效果。

二、RecyclerView的優點

   1、支持局部刷新。
   2、可以自定義item增刪時的動畫。
   3、能夠實現item拖拽和側滑刪除等功能。
   4、默認已實現View的復用,而且回收機制更加完善。

三、RecyclerView的使用方法

首先要在build.gradle文件中添加引用

compile 'com.android.support:recyclerview-v7:26.1.0' 

主頁面布局:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.RecyclerView android:id="@+id/rv_list" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayou> 

item布局:

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <TextView android:id="@+id/tv_content" android:layout_width="match_parent" android:layout_height="50dp" android:gravity="center" android:text="數據" /> </LinearLayout> 

adapter代碼:

public class MyRecycleViewAdapter extends RecyclerView.Adapter<MyRecycleViewAdapter.MyHolder> { private List mList;//數據源 MyRecycleViewAdapter(List list) { mList = list; } //創建ViewHolder並返回,后續item布局里控件都是從ViewHolder中取出 @Override public MyHolder onCreateViewHolder(ViewGroup parent, int viewType) { //將我們自定義的item布局R.layout.item_one轉換為View View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_one, parent, false); //將view傳遞給我們自定義的ViewHolder MyHolder holder = new MyHolder(view); //返回這個MyHolder實體 return holder; } //通過方法提供的ViewHolder,將數據綁定到ViewHolder中 @Override public void onBindViewHolder(MyHolder holder, int position) { holder.textView.setText(mList.get(position).toString()); } //獲取數據源總的條數 @Override public int getItemCount() { return mList.size(); } /** * 自定義的ViewHolder */ class MyHolder extends RecyclerView.ViewHolder { TextView textView; public MyHolder(View itemView) { super(itemView); textView = itemView.findViewById(R.id.tv_content); } } } 

MainActivity代碼:

public class MainActivity extends AppCompatActivity { private RecyclerView mRecycleView; private MyRecycleViewAdapter mAdapter;//適配器 private LinearLayoutManager mLinearLayoutManager;//布局管理器 private List mList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mList = new ArrayList(); mRecycleView = findViewById(R.id.rv_list); //初始化數據 initData(mList); //創建布局管理器,垂直設置LinearLayoutManager.VERTICAL,水平設置LinearLayoutManager.HORIZONTAL mLinearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); //創建適配器,將數據傳遞給適配器 mAdapter = new MyRecycleViewAdapter(mList); //設置布局管理器 mRecycleView.setLayoutManager(mLinearLayoutManager); //設置適配器adapter mRecycleView.setAdapter(mAdapter); } public void initData(List list) { for (int i = 1; i <= 40; i++) { list.add("第" + i + "條數據"); } } } 

Adapter

      使用時需要創建adapter(適配器)類,該類繼承於RecyclerView.Adapter<VH>,其中VH是我們adapter類中創建的一個繼承於RecyclerView.ViewHolder的靜態內部類。

可以看到該適配器類主要有3個方法和1個自定義ViewHolder組成:

  • onCreateViewHolder: 創建ViewHolder並返回,后續item布局里控件都是從ViewHolder中取出。
  • onBindViewHolder:通過方法提供的ViewHolder,將數據綁定到ViewHolder中。
  • getItemCount:獲取數據源總的條數。
  • MyHolder :這是RecyclerView.ViewHolder的實現類,用於初始化item布局中的子控件。需要注意的是,在這個類的構造方法中需要傳遞item布局的View給父類 。

使用方法:

//設置適配器adapter mRecycleView.setAdapter(mAdapter); 

LayoutManager

      布局管理器,通過不同的布局管理器來控制item的排列順序,負責item元素的布局和復用。RecycleView提供了三種布局管理器:

  • LinearLayoutManager:線性布局,以垂直或水平滾動列表方式顯示項目。
  • GridLayoutManager:網格布局,在網格中顯示項目。
  • StaggeredGridLayoutManager:瀑布流布局,在分散對齊網格中顯示項目。

使用方法:

mRecycleView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL,false)); 

運行效果:

 
運行效果

      以上是LinearLayoutManager布局呈現的效果,假如遇到特殊需求,也可以通過繼承 RecyclerView.LayoutManager來自定義LayoutManager,重寫它的方法來實現所需要的效果。

 


ItemDecoration

      RecyclerView可以通過addItemDecoration()設置分割線。Android並沒有提供實現好的分割線,所以任何的分割線樣式都需要用戶自己實現。可以通過繼承RecyclerView.ItemDecoration類來實現。

ItemDecoration源碼:

public abstract static class ItemDecoration { public void onDraw(Canvas c, RecyclerView parent, State state) { onDraw(c, parent); } @Deprecated public void onDraw(Canvas c, RecyclerView parent) { } public void onDrawOver(Canvas c, RecyclerView parent, State state) { onDrawOver(c, parent); } @Deprecated public void onDrawOver(Canvas c, RecyclerView parent) { } @Deprecated public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { outRect.set(0, 0, 0, 0); } public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) { getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(), parent); } } 

該抽象類主要由三個方法組成:

  • onDraw(Canvas c, RecyclerView parent, State state):在Item繪制之前被調用(先於drawChildren),主要用於繪制分割線樣式。
  • onDrawOver(Canvas c, RecyclerView parent, State state):在Item繪制之后被調用(慢於drawChildren),主要用於繪制分割線樣式。
  • getItemOffsets(Rect outRect, View view, RecyclerView parent, State state):通過outRect.set()為每個Item設置一定的偏移量。

      onDrawonDrawOver這兩個方法都是用於繪制分割線,我們在使用時只需要按需求選擇一個進行實現就可以。它們的區別在於執行時間的不同,我們可以通過源碼找到它們的區別。

RecyclerView源碼:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 { @Override public void draw(Canvas c) { super.draw(c); final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDrawOver(c, this, mState); } @Override public void onDraw(Canvas c) { super.onDraw(c); final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get(i).onDraw(c, this, mState); } } } 

View源碼:

public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { public void draw(Canvas canvas) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); } } 

      首先執行的是RecyclerView中重寫的draw()方法,然后會去執行super.draw(),即View的draw()方法。在View的draw()方法中,會先去執行onDraw(),再去執行dispatchDraw()方法,由於RecyclerView重寫了onDraw()方法,所以是先執行了RecyclerView中的onDraw()方法。因此,它們的執行順序為:onDraw()->dispatchDraw()->onDrawOver()。不理解的話可以參照上面的圖多看兩遍。

Google給我們提供了一個實現類:DividerItemDecoration,我們可以參照它去實現自定義的Item Decoration。

DividerItemDecoration源碼:

public class DividerItemDecoration extends RecyclerView.ItemDecoration { public static final int HORIZONTAL = LinearLayout.HORIZONTAL; public static final int VERTICAL = LinearLayout.VERTICAL; private static final String TAG = "DividerItem"; private static final int[] ATTRS = new int[]{ android.R.attr.listDivider }; private Drawable mDivider; private int mOrientation; private final Rect mBounds = new Rect(); public DividerItemDecoration(Context context, int orientation) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); if (mDivider == null) { Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this " + "DividerItemDecoration. Please set that attribute all call setDrawable()"); } a.recycle(); setOrientation(orientation); } public void setOrientation(int orientation) { if (orientation != HORIZONTAL && orientation != VERTICAL) { throw new IllegalArgumentException( "Invalid orientation. It should be either HORIZONTAL or VERTICAL"); } mOrientation = orientation; } public void setDrawable(@NonNull Drawable drawable) { if (drawable == null) { throw new IllegalArgumentException("Drawable cannot be null."); } mDivider = drawable; } @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { if (parent.getLayoutManager() == null || mDivider == null) { return; } if (mOrientation == VERTICAL) { drawVertical(c, parent); } else { drawHorizontal(c, parent); } } private void drawVertical(Canvas canvas, RecyclerView parent) { canvas.save(); final int left; final int right; //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides. if (parent.getClipToPadding()) { left = parent.getPaddingLeft(); right = parent.getWidth() - parent.getPaddingRight(); canvas.clipRect(left, parent.getPaddingTop(), right, parent.getHeight() - parent.getPaddingBottom()); } else { left = 0; right = parent.getWidth(); } final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); parent.getDecoratedBoundsWithMargins(child, mBounds); final int bottom = mBounds.bottom + Math.round(child.getTranslationY()); final int top = bottom - mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } canvas.restore(); } private void drawHorizontal(Canvas canvas, RecyclerView parent) { canvas.save(); final int top; final int bottom; //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides. if (parent.getClipToPadding()) { top = parent.getPaddingTop(); bottom = parent.getHeight() - parent.getPaddingBottom(); canvas.clipRect(parent.getPaddingLeft(), top, parent.getWidth() - parent.getPaddingRight(), bottom); } else { top = 0; bottom = parent.getHeight(); } final int childCount = parent.getChildCount(); for (int i = 0; i < childCount; i++) { final View child = parent.getChildAt(i); parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds); final int right = mBounds.right + Math.round(child.getTranslationX()); final int left = right - mDivider.getIntrinsicWidth(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(canvas); } canvas.restore(); } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { if (mDivider == null) { outRect.set(0, 0, 0, 0); return; } if (mOrientation == VERTICAL) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } } } 

      從源碼中可以看到該類是用系統中的android.R.attr.listDivider來作為分割線,通過DividerItemDecoration構造方法中的setOrientation(orientation)來設置分割線的方向。在getItemOffsets()中利用outRect.set()去設置了繪制的范圍,再在onDraw()中進行真正的繪制。

使用方法:

//設置分割線 mRecycleView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); 

運行效果:


 
帶分割線的列表.png

事件監聽

    RecyclerView並沒有給我們提供現成的點擊事件監聽,需要我們自己去實現。我們可以在RecyclerView的Adapter中自定義一個接口,並創建一個供其他類設置監聽的方法。

public class MyRecycleViewAdapter extends RecyclerView.Adapter<MyRecycleViewAdapter.MyHolder> { private List mList;//數據源 private OnItemClickListener onItemClickListener; /** * 供外部調用設置監聽 * @param onItemClickListener */ public void setOnItemClickListener(OnItemClickListener onItemClickListener) { this.onItemClickListener = onItemClickListener; } /** * 自定義的接口 */ public interface OnItemClickListener { void onItemClick(View view, int position); } //通過方法提供的ViewHolder,將數據綁定到ViewHolder中 @Override public void onBindViewHolder(final MyHolder holder, int position) { holder.textView.setText(mList.get(position).toString()); holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (onItemClickListener != null) { onItemClickListener.onItemClick(v, holder.getAdapterPosition() + 1); } } }); } } 

      以上省略了部分與該內容無關的代碼。當我們定義好接口后,我們在onBindViewHolder()方法中為holder.itemView(itemView是列表中的每一個item項)設置了點擊事件監聽,然后在onClick()中判斷是否有用戶傳遞過onItemClickListener實例進來,有的話會調用他的onItemClick(),將點擊事件轉移到我們的自定義接口上,傳給外面的調用者。調用者代碼如下:

 mAdapter.setOnItemClickListener(new MyRecycleViewAdapter.OnItemClickListener() { @Override public void onItemClick(View view, int position) { Toast.makeText(getApplicationContext(), "第" + position + "條數據", Toast.LENGTH_SHORT).show(); } }); 

      到這里點擊事件就完成了。如果你想實現長按也是同樣的方法,在自定義的接口中多加一個長按的方法,然后holder.itemView調用setOnLongClickListener()去將長按事件轉移到自定義的接口上。


ItemAnimator 動畫

      RecyclerView可以通過mRecyclerView.setItemAnimator(ItemAnimator animator)來設置添加和移除時的動畫效果。ItemAnimator是一個抽象類,RecyclerView為我們提供了一個ItemAnimator的實現類DefaultItemAnimator

使用方法:

//設置動畫效果 mRecycleView.setItemAnimator(new DefaultItemAnimator()); 

      在adapter中添加兩個方法,用於添加和移除Item。這里要注意的是,更新數據集要用notifyItemInserted(position)notifyItemRemoved(position) ,而不是notifyDataSetChanged(),否則沒有動畫效果。

    /** * 添加數據 */ public void addItem() { mList.add(0, "new "); notifyItemInserted(0); } /** * 移除數據 * @param position */ public void removeItem(int position) { mList.remove(position); notifyItemRemoved(position); } 

效果是按下底部“添加”按鈕會在頂部插入數據,點擊列表中的Item則刪除該條數據。

 
添加刪除動畫效果.gif

      如果我們對這種動畫效果不滿意,也可以去自定義各種動畫效果。目前github上有許多開源的項目,例如 RecyclerViewItemAnimators,我們可以直接去引用或學習它的動畫效果。

 

結論:以上就是RecyclerView的基本用法,看到這里可能很多人會覺得它比ListView復雜得多,很多東西都需要自己去定義,但正是這種定制性使得它具有良好的擴展性,我們可以根據具體需求去自定義自己想要實現的效果,相信你也會喜歡上這個控件!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM