Android RecyclerView详解
介紹
RecyclerView用于在有限的窗口展現(xiàn)大量的數(shù)據(jù),其實早已經(jīng)有了類似的控件,如ListView、GridView,那么相比它們,RecyclerView有什么樣優(yōu)勢呢?
RecyclerView標(biāo)準(zhǔn)化了ViewHolder,而且異常的靈活,可以輕松實現(xiàn)ListView實現(xiàn)不了的樣式和功能,通過布局管理器LayoutManager可控制Item的布局方式,通過設(shè)置Item操作動畫自定義Item添加和刪除的動畫,通過設(shè)置Item之間的間隔樣式,自定義間隔。
可實現(xiàn)效果
設(shè)置布局管理器以控制Item的布局方式,橫向、豎向以及瀑布流方式。
可設(shè)置Item操作的動畫(刪除或者添加等)
可設(shè)置Item的間隔樣式(可繪制)
關(guān)于Item的點擊和長按事件,需要用戶自己去實現(xiàn)
使用
- 使用RecyclerView時候,必須指定一個適配器Adapter和一個布局管理器LayoutManager。
- 適配器繼承RecyclerView.Adapter類,具體實現(xiàn)類似ListView的適配器,取決于數(shù)據(jù)信息以及展示的UI。
- 布局管理器用于確定RecyclerView中Item的展示方式以及決定何時復(fù)用已經(jīng)不可見的Item,避免重復(fù)創(chuàng)建以及執(zhí)行高成本的findViewById()方法
用法
示例
mRecyclerView = (RecyclerView) findViewById(R.id.my_recycler_view); LinearLayoutManager mLayoutManager=new LinearLayoutManager(this); // 設(shè)置布局管理器 mRecyclerView.setLayoutManager(mLayoutManager); // 設(shè)置adapter mRecyclerView.setAdapter(mAdapter); // 設(shè)置Item添加和移除的動畫 mRecyclerView.setItemAnimator(new DefaultItemAnimator()); // 設(shè)置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);// 設(shè)置布局管理器DefaultItemAnimator itemAnimator = new DefaultItemAnimator();recyclerView .setItemAnimator(itemAnimator);// 設(shè)置Item添加和移除的動畫itemAnimator.setSupportsChangeAnimations(false);recyclerView.setAdapter(new VideoRecyclerViewAdapter(getVideoList(), getActivity()));}public List<VideoBean> getVideoList() {List<VideoBean> videoList = new ArrayList<>();//...添加數(shù)據(jù)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 (網(wǎng)格):以網(wǎng)格方式展示Item
- StaggeredGridLayoutManager(瀑布流): 以瀑布流方式展示Item
適配器:RecyclerView.Adapter
RecyclerView.Adapter的使用方式和ListView的ListAdapter 類似,向RecyclerView提供顯示的數(shù)據(jù)。
但是RecyclerView.Adapter做了一件了不起的優(yōu)化,那就是RecyclerView.Adapter的
方法能夠保證當(dāng)前RecyclerView是確實需要你創(chuàng)建一個新的ViewHolder對象。而ListView的ListAdapter 對應(yīng)的方法
@Overridepublic View getView(int i, View view, ViewGroup viewGroup)需要在方法內(nèi)部判斷是重新創(chuàng)建一個View還是刷新一個View的數(shù)據(jù),而不明所以的客戶可能每次都會返回一個新創(chuàng)建的View造成ListView的卡頓和資源的浪費;創(chuàng)建和刷新作為兩個不同的功能本來就應(yīng)該在兩個方法中實現(xiàn)—慶幸的是RecyclerView.Adapter解決了這個問題
視圖容器:RecyclerView.ViewHolder
RecyclerView中強制客戶使用ViewHolder,談及ListView的時候就經(jīng)常說到使用ViewHolder來進(jìn)行優(yōu)化。使用ViewHolder其中一點好處是能夠避免重復(fù)調(diào)用方法findViewById(),對當(dāng)前item的View中的子View進(jìn)行管理。
ViewHolder 描述RecylerView中某個位置的itemView和元數(shù)據(jù)信息,屬于Adapter的一部分。其實該類通常用于保存 findViewById 的結(jié)果
ViewHolder的mFlags屬性
- FLAG_BOUND ——ViewHolder已經(jīng)綁定到某個位置,mPosition、mItemId、mItemViewType都有效
- FLAG_UPDATE ——ViewHolder綁定的View對應(yīng)的數(shù)據(jù)過時需要重新綁定,mPosition、mItemId還是一致的
- FLAG_INVALID ——ViewHolder綁定的View對應(yīng)的數(shù)據(jù)無效,需要完全重新綁定不同的數(shù)據(jù)
- FLAG_REMOVED ——ViewHolder對應(yīng)的數(shù)據(jù)已經(jīng)從數(shù)據(jù)集移除
- FLAG_NOT_RECYCLABLE ——ViewHolder不能復(fù)用
- FLAG_RETURNED_FROM_SCRAP ——這個狀態(tài)的ViewHolder會加到scrap list被復(fù)用。
- FLAG_CHANGED ——ViewHolder內(nèi)容發(fā)生變化,通常用于表明有ItemAnimator動畫
- FLAG_IGNORE ——ViewHolder完全由LayoutManager管理,不能復(fù)用
- FLAG_TMP_DETACHED ——ViewHolder從父RecyclerView臨時分離的標(biāo)志,便于后續(xù)移除或添加回來
- FLAG_ADAPTER_POSITION_UNKNOWN ——ViewHolder不知道對應(yīng)的Adapter的位置,直到綁定到一個新位置
- FLAG_ADAPTER_FULLUPDATE ——方法 addChangePayload(null) 調(diào)用時設(shè)置
間隔樣式:RecyclerView.ItemDecoration
- 用于繪制itemView之間的一些特殊UI,比如在itemView之前設(shè)置空白區(qū)域、畫線等。
- RecyclerView 將itemView和裝飾UI分隔開來,裝飾UI即 ItemDecoration ,主要用于繪制item間的分割線、高亮或者margin等
- 通過recyclerView.addItemDecoration(new DividerDecoration(this))對item添加裝飾;對RecyclerView設(shè)置多個ItemDecoration,列表展示的時候會遍歷所有的ItemDecoration并調(diào)用里面的繪制方法,對Item進(jìn)行裝飾。
- public void onDraw(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之前調(diào)用,所以這有可能被Item的內(nèi)容所遮擋
- public void onDrawOver(Canvas c, RecyclerView parent) 裝飾的繪制在Item條目繪制之后調(diào)用,因此裝飾將浮于Item之上
- public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) 與padding或margin類似,LayoutManager在測量階段會調(diào)用該方法,計算出每一個Item的正確尺寸并設(shè)置偏移量
展示效果和ListView基本上無差別,但是Item之間并沒有分割線,在xml去找divider屬性的時候,發(fā)現(xiàn)RecyclerView沒有divider屬性,當(dāng)然也可以在Item布局中加上分割線,但是這樣做并不是很優(yōu)雅。
其實RecyclerView是支持自定義間隔樣式的。通過
來設(shè)置我們定義好的間隔樣式
自定義間隔樣式需要繼承RecyclerView.ItemDecoration類,該類是個抽象類,主要有三個方法
- onDraw(Canvas c, RecyclerView parent, State state):在Item繪制之前被調(diào)用,該方法主要用于繪制間隔樣式
- onDrawOver(Canvas c, RecyclerView parent, State state):在Item繪制之前被調(diào)用,該方法主要用于繪制間隔樣式
- getItemOffsets(Rect outRect, View view, RecyclerView parent, State state):設(shè)置item的偏移量,偏移的部分用于填充間隔樣式,在RecyclerView的onMesure()中會調(diào)用該方法
onDraw()和onDrawOver()這兩個方法都是用于繪制間隔樣式,我們只需要復(fù)寫其中一個方法即可。直接來看一下自定義的間隔樣式的實現(xiàn),參考官方實例
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) {// 獲取默認(rèn)主題的屬性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);}} }然后在代碼中設(shè)置RecyclerView的間隔樣式
mRecyclerView.addItemDecoration(new MyDividerItemDecoration(this, LinearLayoutManager.VERTICAL));動畫:RecyclerView.ItemAnimator
RecyclerView可以設(shè)置列表中Item刪除和添加的動畫,在v7包中給我們提供了一種默認(rèn)的Item刪除和添加的動畫,如果沒有特殊的需求,默認(rèn)使用這個動畫即可
// 設(shè)置Item添加和移除的動畫 mRecyclerView.setItemAnimator(new DefaultItemAnimator());設(shè)置的動畫用于在 item 項數(shù)據(jù)變化時的動畫效果
當(dāng)調(diào)用 Adapter 的 notifyItemChanged、notifyItemInserted、notifyItemMoved 等方法,會觸發(fā)該對象顯示相應(yīng)的動畫。
RecyclerView 的 ItemAnimator 使得 item 的動畫實現(xiàn)變得簡單而樣式豐富,我們可以自定義 item 項不同操作(如添加,刪除)的動畫效果;
ItemAnimator 觸發(fā)于以下三種事件:
- 某條數(shù)據(jù)被插入到數(shù)據(jù)集合中 ,對應(yīng) public final void notifyItemInserted(int position) 方法
- 從數(shù)據(jù)集合中移除某條數(shù)據(jù) ,對應(yīng) public final void notifyItemRemoved(int position) 方法
- 更改數(shù)據(jù)集合中的某條數(shù)據(jù),對應(yīng) public final void notifyItemChanged(int position) 方法
注意:notifyDataSetChanged(),會觸發(fā)列表的重繪,并不會出現(xiàn)任何動畫效果
使用:Animator使用到的邏輯比較多,因此最方便的就是使用第三方庫:https://github.com/wasabeef/recyclerview-animators
點擊事件
RecyclerView并沒有像ListView一樣暴露出Item點擊事件或者長按事件處理的api,也就是說使用RecyclerView時候,需要我們自己來實現(xiàn)Item的點擊和長按等事件的處理。
實現(xiàn)方法有很多
- 可以監(jiān)聽RecyclerView的Touch事件然后判斷手勢做相應(yīng)的處理
- 可以通過在綁定ViewHolder的時候設(shè)置監(jiān)聽,然后通過Apater回調(diào)出去
- 使用點擊、長按事件支持類
第二種方法:在綁定ViewHolder的時候設(shè)置監(jiān)聽,通過Apater回調(diào)出去 Adapter 的完整代碼
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter .ViewHolder>{/*** 展示數(shù)據(jù)*/private ArrayList<String> mData;/*** 事件回調(diào)監(jiān)聽*/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);}/*** 設(shè)置回調(diào)監(jiān)聽* * @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) {// 綁定數(shù)據(jù)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);}//表示此事件已經(jīng)消費,不會觸發(fā)單擊事件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 設(shè)置 Adapter 事件監(jiān)聽
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 ->
總結(jié)
目前而言,我們已經(jīng)知道RecyclerView的一些功能如下
- 水平列表展示,設(shè)置LayoutManager的方向性
- 豎直列表展示,設(shè)置LayoutManager的方向性
- 自定義間隔,RecyclerView.addItemDecoration()
- Item添加和刪除動畫,RecyclerView.setItemAnimator()
所以在項目中如果再遇見列表類的布局,就可以優(yōu)先考慮使用 RecyclerView,更靈活更快捷的使用方式會給編碼帶來不一樣的體驗
總結(jié)
以上是生活随笔為你收集整理的Android RecyclerView详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 取消 AndroidStudio 启动时
- 下一篇: android sina oauth2.