Android RecyclerView详解
介紹
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); LinearLayoutManager mLayoutManager=new LinearLayoutManager(this); // 設置布局管理器 mRecyclerView.setLayoutManager(mLayoutManager); // 設置adapter mRecyclerView.setAdapter(mAdapter); // 設置Item添加和移除的動畫 mRecyclerView.setItemAnimator(new DefaultItemAnimator()); // 設置Item之間間隔樣式 mRecyclerView.addItemDecoration(mDividerItemDecoration);基本使用
首先需要在在 build.gradle 文件中引入 RecyclerView 類
compile 'com.android.support:recyclerview-v7:23.4.0'Fragment代碼
package com.demo.fragment;import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup;import com.demo.R; import com.demo.adapter.VideoRecyclerViewAdapter; import com.demo.bean.VideoBean;import java.util.ArrayList; import java.util.List;public class ListViewFragment extends Fragment{public static ListViewFragment newInstance() {return new ListViewFragment();}@Nullable@Overridepublic View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {View view = inflater.inflate(R.layout.activity_recycler_view, container, false);initView(view);return view;}private void initView(View view) {RecyclerView recyclerView = view.findViewById(R.id.rv);LinearLayoutManager layoutManager=new LinearLayoutManager(getActivity());layoutManager.setOrientation(LinearLayoutManager.VERTICAL);recyclerView.setLayoutManager(layoutManager);// 設置布局管理器DefaultItemAnimator itemAnimator = new DefaultItemAnimator();recyclerView .setItemAnimator(itemAnimator);// 設置Item添加和移除的動畫itemAnimator.setSupportsChangeAnimations(false);recyclerView.setAdapter(new VideoRecyclerViewAdapter(getVideoList(), getActivity()));}public List<VideoBean> getVideoList() {List<VideoBean> videoList = new ArrayList<>();//...添加數據return videoList;} }R.layout.activity_recycler_view
<?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/rv"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>RecyclerView適配器Adapter代碼
package com.demo.adapter;import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView;import com.bumptech.glide.Glide; import com.demo.R; import com.demo.bean.VideoBean; import com.demo.view.CustomVideoView ;import java.util.List;public class VideoRecyclerViewAdapter extends RecyclerView.Adapter<VideoRecyclerViewAdapter.VideoHolder> {private List<VideoBean> videos;private Context context;public VideoRecyclerViewAdapter(List<VideoBean> videos, Context context) {this.videos = videos;this.context = context;}@Overridepublic VideoHolder onCreateViewHolder(ViewGroup parent, int viewType) {View itemView = LayoutInflater.from(context).inflate(R.layout.item_video_auto_play, parent, false);return new VideoHolder(itemView);}@Overridepublic void onBindViewHolder(final VideoHolder holder, int position) {VideoBean videoBean = videos.get(position);holder.title.setText(videoBean.getTitle());holder.videoView .setPlayUrl(videoBean.getUrl());}@Overridepublic int getItemCount() {return videos.size();}public class VideoHolder extends RecyclerView.ViewHolder {private CustomVideoView videoView;private TextView title;VideoHolder(View itemView) {super(itemView);videoView= (CustomVideoView )itemView.findViewById(R.id.video_player);int widthPixels = context.getResources().getDisplayMetrics().widthPixels;videoView .setLayoutParams(new LinearLayout.LayoutParams(widthPixels, widthPixels / 16 * 9));title = itemView.findViewById(R.id.tv_title);}}}布局管理器:RecyclerView.LayoutManager
上述代碼中mLayoutManager 對象是布局管理器,RecyclerView提供了三種布局管理器:
- LinerLayoutManager(線性):以垂直或者水平列表方式展示Item
- GridLayoutManager (網格):以網格方式展示Item
- StaggeredGridLayoutManager(瀑布流): 以瀑布流方式展示Item
適配器:RecyclerView.Adapter
RecyclerView.Adapter的使用方式和ListView的ListAdapter 類似,向RecyclerView提供顯示的數據。
但是RecyclerView.Adapter做了一件了不起的優化,那就是RecyclerView.Adapter的
方法能夠保證當前RecyclerView是確實需要你創建一個新的ViewHolder對象。而ListView的ListAdapter 對應的方法
@Overridepublic View getView(int i, View view, ViewGroup viewGroup)需要在方法內部判斷是重新創建一個View還是刷新一個View的數據,而不明所以的客戶可能每次都會返回一個新創建的View造成ListView的卡頓和資源的浪費;創建和刷新作為兩個不同的功能本來就應該在兩個方法中實現—慶幸的是RecyclerView.Adapter解決了這個問題
視圖容器:RecyclerView.ViewHolder
RecyclerView中強制客戶使用ViewHolder,談及ListView的時候就經常說到使用ViewHolder來進行優化。使用ViewHolder其中一點好處是能夠避免重復調用方法findViewById(),對當前item的View中的子View進行管理。
ViewHolder 描述RecylerView中某個位置的itemView和元數據信息,屬于Adapter的一部分。其實該類通常用于保存 findViewById 的結果
ViewHolder的mFlags屬性
- FLAG_BOUND ——ViewHolder已經綁定到某個位置,mPosition、mItemId、mItemViewType都有效
- FLAG_UPDATE ——ViewHolder綁定的View對應的數據過時需要重新綁定,mPosition、mItemId還是一致的
- FLAG_INVALID ——ViewHolder綁定的View對應的數據無效,需要完全重新綁定不同的數據
- FLAG_REMOVED ——ViewHolder對應的數據已經從數據集移除
- FLAG_NOT_RECYCLABLE ——ViewHolder不能復用
- FLAG_RETURNED_FROM_SCRAP ——這個狀態的ViewHolder會加到scrap list被復用。
- FLAG_CHANGED ——ViewHolder內容發生變化,通常用于表明有ItemAnimator動畫
- FLAG_IGNORE ——ViewHolder完全由LayoutManager管理,不能復用
- FLAG_TMP_DETACHED ——ViewHolder從父RecyclerView臨時分離的標志,便于后續移除或添加回來
- FLAG_ADAPTER_POSITION_UNKNOWN ——ViewHolder不知道對應的Adapter的位置,直到綁定到一個新位置
- FLAG_ADAPTER_FULLUPDATE ——方法 addChangePayload(null) 調用時設置
間隔樣式:RecyclerView.ItemDecoration
- 用于繪制itemView之間的一些特殊UI,比如在itemView之前設置空白區域、畫線等。
- RecyclerView 將itemView和裝飾UI分隔開來,裝飾UI即 ItemDecoration ,主要用于繪制item間的分割線、高亮或者margin等
- 通過recyclerView.addItemDecoration(new DividerDecoration(this))對item添加裝飾;對RecyclerView設置多個ItemDecoration,列表展示的時候會遍歷所有的ItemDecoration并調用里面的繪制方法,對Item進行裝飾。
- public void onDraw(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之前調用,所以這有可能被Item的內容所遮擋
- public void onDrawOver(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之后調用,因此裝飾將浮于Item之上
- public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) 與padding或margin類似,LayoutManager在測量階段會調用該方法,計算出每一個Item的正確尺寸并設置偏移量
展示效果和ListView基本上無差別,但是Item之間并沒有分割線,在xml去找divider屬性的時候,發現RecyclerView沒有divider屬性,當然也可以在Item布局中加上分割線,但是這樣做并不是很優雅。
其實RecyclerView是支持自定義間隔樣式的。通過
來設置我們定義好的間隔樣式
自定義間隔樣式需要繼承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(0);a.recycle();setOrientation(orientation);}@Overridepublic void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {// 繪制間隔if (mOrientation == VERTICAL_LIST) {drawVertical(c, parent);} else {drawHorizontal(c, parent);}}@Overridepublic 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);}}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 = 0; 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 = 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 +Math.round(ViewCompat.getTranslationX(child));final int right = left + mDivider.getIntrinsicHeight();mDivider.setBounds(left, top, right, bottom);mDivider.draw(c);}} }然后在代碼中設置RecyclerView的間隔樣式
mRecyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.VERTICAL));動畫:RecyclerView.ItemAnimator
RecyclerView可以設置列表中Item刪除和添加的動畫,在v7包中給我們提供了一種默認的Item刪除和添加的動畫,如果沒有特殊的需求,默認使用這個動畫即可
// 設置Item添加和移除的動畫 mRecyclerView.setItemAnimator(new DefaultItemAnimator());設置的動畫用于在 item 項數據變化時的動畫效果
當調用 Adapter 的 notifyItemChanged、notifyItemInserted、notifyItemMoved 等方法,會觸發該對象顯示相應的動畫。
RecyclerView 的 ItemAnimator 使得 item 的動畫實現變得簡單而樣式豐富,我們可以自定義 item 項不同操作(如添加,刪除)的動畫效果;
ItemAnimator 觸發于以下三種事件:
- 某條數據被插入到數據集合中 ,對應 public final void notifyItemInserted(int position) 方法
- 從數據集合中移除某條數據 ,對應 public final void notifyItemRemoved(int position) 方法
- 更改數據集合中的某條數據,對應 public final void notifyItemChanged(int position) 方法
注意:notifyDataSetChanged(),會觸發列表的重繪,并不會出現任何動畫效果
使用:Animator使用到的邏輯比較多,因此最方便的就是使用第三方庫:https://github.com/wasabeef/recyclerview-animators
點擊事件
RecyclerView并沒有像ListView一樣暴露出Item點擊事件或者長按事件處理的api,也就是說使用RecyclerView時候,需要我們自己來實現Item的點擊和長按等事件的處理。
實現方法有很多
- 可以監聽RecyclerView的Touch事件然后判斷手勢做相應的處理
- 可以通過在綁定ViewHolder的時候設置監聽,然后通過Apater回調出去
- 使用點擊、長按事件支持類
第二種方法:在綁定ViewHolder的時候設置監聽,通過Apater回調出去 Adapter 的完整代碼
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter .ViewHolder>{/*** 展示數據*/private ArrayList<String> mData;/*** 事件回調監聽*/private RecyclerViewAdapter.OnItemClickListener onItemClickListener;public RecyclerViewAdapter(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(0, "new Item");notifyItemInserted(0);}/*** 刪除Item*/public void deleteItem() {if(mData == null || mData.isEmpty()) {return;}mData.remove(0);notifyItemRemoved(0);}/*** 設置回調監聽* * @param listener*/public void setOnItemClickListener(MyAdapter.OnItemClickListener listener) {this.onItemClickListener = listener;}@Overridepublic ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {// 實例化展示的viewView v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_rv_item, parent, false);// 實例化viewholderViewHolder viewHolder = new ViewHolder(v);return viewHolder;}@Overridepublic void onBindViewHolder(final ViewHolder holder, int position) {// 綁定數據holder.mTv.setText(mData.get(position));holder.itemView.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(final View v) {if(onItemClickListener != null) {int pos = holder.getLayoutPosition();onItemClickListener.onItemClick(holder.itemView, pos);}}});holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {@Overridepublic boolean onLongClick(View v) {if(onItemClickListener != null) {int pos = holder.getLayoutPosition();onItemClickListener.onItemLongClick(holder.itemView, pos);}//表示此事件已經消費,不會觸發單擊事件return true;}});}@Overridepublic int getItemCount() {return mData == null ? 0 : 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);} }Activity 設置 Adapter 事件監聽
mAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {@Overridepublic void onItemClick(View view, int position) {Toast.makeText(MyActivity.this,"click " + position + " item", Toast.LENGTH_SHORT).show();}@Overridepublic void onItemLongClick(View view, int position) {Toast.makeText(MyActivity.this,"long click " + position + " item", Toast.LENGTH_SHORT).show();} });第三種方法:使用點擊、長按事件支持類代碼
參考 Hugo 的文章:Getting your clicks on RecyclerView
res -> values -> ids.xml ->
總結
目前而言,我們已經知道RecyclerView的一些功能如下
- 水平列表展示,設置LayoutManager的方向性
- 豎直列表展示,設置LayoutManager的方向性
- 自定義間隔,RecyclerView.addItemDecoration()
- Item添加和刪除動畫,RecyclerView.setItemAnimator()
所以在項目中如果再遇見列表類的布局,就可以優先考慮使用 RecyclerView,更靈活更快捷的使用方式會給編碼帶來不一樣的體驗
總結
以上是生活随笔為你收集整理的Android RecyclerView详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 取消 AndroidStudio 启动时
- 下一篇: Android 自定义控件 ViewPa