說到RecyclerView,相信你們都不陌生,它是咱們經典級ListView的升級版,升級後的RecyclerView展示了極大的靈活性。同時內部直接封裝了ViewHolder,不用咱們本身定義ViewHolder就能實現item的回收和複用功能。固然它確定不止這些好處,好比咱們能夠自定義分割線,能夠更加方便的實現列表的佈局方式等等。雖然說咱們本身在第一次使用時,會比使用listView和gridView稍微的複雜一些,須要自定義的也多了一點,可是它卻更好的體現了靈活性,能夠隨本身的喜愛來隨便的定義,固然最主要的是能更好的複用,只需一次的定義,卻可隨處的複用。python
下面,咱們來好好的學習下它的使用。android
首先,咱們要是用RecyclerView必須引入support-V7包,拿android studio來舉例:微信
先打開File->選擇Project Structure,以後在左邊Modules選擇你的項目,而後在點擊右邊的Dependencies,而後點擊綠色的+號選擇添加Library,而後找到recyclerview-v7雙擊加入到依賴庫中。而後能夠在build.gradle中查看:app
dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') testCompile 'junit:junit:4.12' compile 'com.android.support:appcompat-v7:23.3.0' compile 'com.android.support:recyclerview-v7:23.3.0' }
能夠找dependencies 中找到對recyclerview的支持,則說明添加成功,若是尚未的,能夠clean下本身的工程。ide
接下來,咱們就進入Recyclerview的學習。RecyclerView的學習目標就是如下四個方法,把如下四個方法徹底的掌握了,也就真正的掌握了RecyclerView。佈局
RecyclerView.setAdapter:用來設置adapter,顯示數據
RecyclerView.setLayoutManager :用來設置顯示佈局的,目前系統給出三種佈局,分別是垂直,水平和瀑布流式佈局
RecyclerView.setItemAnimator :用來設置顯示動畫的
RecyclerView.addItemDecoration :用來設置列表分割線的學習
接下來咱們就學習怎麼使用以上四個方法來真正掌握Recyclerview的使用。要使用Recyclerview,咱們必須先定義一個類(CustomRecyclerAdapter)並繼承Recyclerview.Adapter,且實現它裏面的方法,代碼以下:gradle
public class CustomRecyclerAdapter extends RecyclerView.Adapter<CustomRecyclerAdapter.ViewHolderHelper>{ @Override public ViewHolderHelper onCreateViewHolder(ViewGroup parent, int viewType) { return null; } @Override public void onBindViewHolder(ViewHolderHelper holder, int position) { } @Override public int getItemCount() { return 0; } public class ViewHolderHelper extends RecyclerView.ViewHolder{ public ViewHolderHelper(View itemView) { super(itemView); } } }
在咱們還沒正式開始使用以前,先大致上瞭解下上面三個方法是作什麼的:動畫
A. onCreateViewHolder()方法:該方法就是將佈局文件轉化爲View並傳遞給RecyclerView封裝好的ViewHolder。ui
B. onBindViewHolder()方法:該方法將會在固定的位置上把ViewHolder裏的itemView數據映射在item中。
C. getItemCount()方法:該方法和listView中的getCount()同樣,都是返回Item的個數。
瞭解了這三個方法,咱們來先實現最簡單的應用,把咱們的數據顯示在app中。
首先,咱們建立一個佈局文件recycler_view.xml,以下
<?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/recycler_view" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.v7.widget.RecyclerView> </LinearLayout>
建立RecycerActivity用來加載佈局文件:
public class RecycerActivity extends Activity { private RecyclerView mRecyclerView; private List<String> mData; private CustomRecyclerAdapter mCustomRecyclerAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.recycer_view); mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view); initData(); //線性佈局管理器 recyclerViewLayoutManager = new LinearLayoutManager(this); //設置佈局管理器 mRecyclerView.setLayoutManager(recyclerViewLayoutManager); //設置顯示動畫 mRecyclerView.setItemAnimator(new DefaultItemAnimator()); //設置adapter mCustomRecyclerAdapter = new CustomRecyclerAdapter(mData); mRecyclerView.setAdapter(mCustomRecyclerAdapter); } private void initData() { mData = new ArrayList<String>(); for(int i = 0; i < 10; i++){ mData.add("第"+i+"item"); } } }
通過修改的CustomRecyclerAdapter 以下,
public class CustomRecyclerAdapter extends RecyclerView.Adapter<CustomRecyclerAdapter.ViewHolderHelper>{ private List<String> mData; public CustomRecyclerAdapter(List<String> data) { mData = data; } @Override public ViewHolderHelper onCreateViewHolder(ViewGroup parent, int viewType) { //onCreateViewHolder方法就是將佈局文件轉化爲View並傳遞給RecyclerView封裝好的ViewHolder View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view, parent, false); return new ViewHolderHelper(view); } @Override public void onBindViewHolder(ViewHolderHelper holder, int position) { holder.textView.setText(mData.get(position)); } @Override public int getItemCount() { return mData.size(); } }
item_view.xml佈局文件
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_item" android:layout_toRightOf="@+id/iv_item" android:layout_width="match_parent" android:layout_height="30dp" android:text="I am item view"/> </RelativeLayout>
好,基礎工做已作足,咱們先來看看效果吧
已經顯示出來了,不過,你們看着是否是很彆扭呢,連個分割線也沒有,還不如listView呢,別急,咱們在上面也提到過,RecyclerView給了咱們最大的發揮自由度,它自己並無給定列表的分割線,這是須要咱們本身定義的。由此咱們來定義本身的分割線。自定義分割線是須要咱們繼承RecyclerView.ItemDecoration類,並實現它的onDraw()方法。請看代碼:
public class DividerItemDecoration extends RecyclerView.ItemDecoration{ public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; private int mOrientation; private Context mContext; private TextPaint mTextPaint; private float listDividerSize = 2; private int listDividerColor; public DividerItemDecoration(Context context,int orientation){ mContext = context; mTextPaint = new TextPaint(); mTextPaint.setColor(Color.RED); 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) { super.onDraw(c, parent); if(mOrientation == HORIZONTAL_LIST){ drawHorizontal(c, parent); }else{ drawVertical(c, parent); } } /** * 繪製垂直分割線 * @param c * @param parent */ private void drawVertical(Canvas c, RecyclerView parent) { //分割線的左邊界 = 子View的左padding值 int rectLeft = parent.getPaddingLeft(); //分割線的右邊界 = 子View的寬度 - 子View的右padding值 int rectRight = parent.getWidth() - parent.getPaddingRight(); int childCount = parent.getChildCount(); for(int i = 0; i < childCount; i ++){ View child = parent.getChildAt(i); RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); // 分割線的top = 子View的底部 + 子View的margin值 int rectTop = child.getBottom() + layoutParams.bottomMargin; // 分割線的bottom = 分割線的top + 分割線的高度 float rectBottom = rectTop + listDividerSize; c.drawRect(rectLeft,rectTop,rectRight,rectBottom,mTextPaint); } } /** * 繪製水平分割線 * @param c * @param parent */ private void drawHorizontal(Canvas c, RecyclerView parent) { //分割線的上邊界 = 子View的上padding值 int rectTop = parent.getPaddingTop(); //分割線的下邊界 = 子View的高度 - 子View的底部padding值 int rectBottom = parent.getHeight() - parent.getPaddingBottom(); int childCount = parent.getChildCount(); for(int i = 0; i < childCount; i ++){ View child = parent.getChildAt(i); RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams(); //分割線的Left = 子View的右邊界 + 子View的左margin值 int rectLeft = child.getRight() + layoutParams.rightMargin; //分割線的right = 分割線的Left + 分割線的寬度 float rectRight = rectLeft + listDividerSize; c.drawRect(rectLeft,rectTop,rectRight,rectBottom,mTextPaint); } } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); if(mOrientation == VERTICAL_LIST){ outRect.set(0,0,0,(int)listDividerSize); } else{ outRect.set(0,0,(int)listDividerSize,0); } } }
代碼很好理解,這裏考慮了兩個狀況,分別是垂直和水平的佈局,而後再ondraw()裏面計算出四角邊值,最後直接繪製一個矩形便可。
在RecycerActivity 中的onCreate中添加上一句
mRecyclerView.addItemDecoration(new DividerItemDecoration(this,DividerItemDecoration.VERTICAL_LIST));
如今再來看看效果。
如今已顯示出來了,分割線也出來了,在這裏只列出了垂直方向的佈局,就再也不列出其餘樣式的佈局代碼了,夥伴們可自行寫寫看。
或許有經驗的小夥伴們已經知道咱們的RecyclerView本身是沒有實現點擊事件的,這裏須要咱們來根據業務的需求本身來實現。這裏咱們利用事件回調機制來完成事件的觸發。
首先咱們須要在CustomRecyclerAdapter中定義一個接口,並在其中定義兩個可用的事件方法,以下:
public interface OnItemClickListener{ void onItemClickListener(); void onLongItemClickListener(); }
這裏提供了用於點擊和長按的事件方法,接下來咱們須要對外暴露該接口用於被調用
public void setOnClickItemListener(OnItemClickListener onItemClickListener){ mOnItemClickListener = onItemClickListener; }
而後咱們能夠在ViewHolderHelper 作以下的修改:
public class ViewHolderHelper extends RecyclerView.ViewHolder{ private TextView textView; public ViewHolderHelper(View itemView) { super(itemView); textView = (TextView)itemView.findViewById(R.id.tv_item); textView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mOnItemClickListener.onItemClickListener(); } }); textView.setOnLongClickListener(new View.OnLongClickListener() { @Override public boolean onLongClick(View v) { mOnItemClickListener.onLongItemClickListener(); return false; } }); } }
首先獲得咱們itemView中的textView對應的Id,而後爲textView添加事件,可是隻是添加卻並不實現,它的用意是誰調用誰實現。
最後在咱們的RecycerActivity中添加以下代碼,請看:
mCustomRecyclerAdapter.setOnClickItemListener(new CustomRecyclerAdapter.OnItemClickListener() { @Override public void onItemClickListener() { Toast.makeText(getContext(),"單擊",Toast.LENGTH_SHORT).show(); } @Override public void onLongItemClickListener() { Toast.makeText(getContext(),"長單擊",Toast.LENGTH_SHORT).show(); } });
下面在來看看是否能夠實現事件的觸發了呢?
ok,學到這裏你們至少對RecylerView有了一個初步的認識,可是咱們一想,這樣寫的話確定達不到咱們標題所說的無限複用,甚至連複用也高不可攀,是的,這樣寫是不可能完成複用的,接下來咱們一步一步的慢慢調整,讓它能夠支持一次編寫N次複用,達到極大多數的重複使用,即便不符合需求,咱們也只須要修改丁點便可知足需求,這是咱們的目標,接下來一步一步的實現。
首先,咱們先整理下,看看能夠調整哪些目標能逐步的實現重複使用的目標:
1. 數據類型:咱們在使用List集合時,是沒法固定類型的,有多是String,int等等類型,因此咱們不該該固定爲哪種類型。
2. 在onCreateViewHolder方法中,它須要映射一個佈局文件並轉化爲View或是一個自定義View傳遞給RecyclerView封裝好的ViewHolder,爲了能夠達到複用,因此咱們就不能夠在此直接映射佈局文件。
3. 在onBindViewHolder方法中也不該該直接爲itemView設置屬性,如上面的:holder.textView.setText(mData.get(position));
4. 咱們不該該在ViewHolder的構造方法中直接獲取咱們的itemView,並給它添加觸發事件
以上幾個是咱們能很直觀的獲得的能重構的問題所在,至於其餘的不容易想到的咱們再重構的時候慢慢講解。如今咱們逐一的解決上面的問題,使咱們能更達到重複使用的目的。
1,針對數據類型的不一致,咱們能夠根據具體的使用場景利用泛型進行傳遞到Adapter中,好比:咱們再定義CustomRecyclerAdapter時使用泛型,讓調用者傳遞過來它所擁有的類型,這樣咱們就能夠不用考慮類型的不一致了。請看下面片斷代碼
public class CustomRecyclerAdapter<T> extends RecyclerView.Adapter<CustomViewHolderHelper>{ private Context mContext; private List<T> mData; private CustomOnItemClickListener mOnItemClickListener; public CustomRecyclerAdapter(Context context, List<T> data) { mContext = context; mData = data; } } ...
RecycerActivity中在調用時能夠這樣使用:
mCustomRecyclerAdapter = new CustomRecyclerAdapter
這樣就能夠把類型給肯定下來了,同時也解決了問題1的複用。
2 , 在onCreateViewHolder方法中,和問題一的解決方案是同樣的,咱們把須要的itemView給傳遞過去而不是固定寫死在方法中
public class CustomRecyclerAdapter<T> extends RecyclerView.Adapter<CustomViewHolderHelper>{ private Context mContext; private List<T> mData; protected int mLayoutResId; private CustomOnItemClickListener mOnItemClickListener; public CustomRecyclerAdapter(Context context, int layoutResId, List<T> data) { mContext = context; mLayoutResId = layoutResId; mData = data; } /** * Called when RecyclerView needs a new ViewHolder of the given type to represent * an item. * 當 RecyclerView 依據給出的類型須要一個新的 ViewHolder 去展現一個 item 時,該方法將會被調用 * * 這個給出的類型是在 getItemViewType返回的,默認返回 0 。 * * @param parent * @param viewType * @return */ @Override public CustomViewHolderHelper onCreateViewHolder(ViewGroup parent, int viewType) { //onCreateViewHolder方法就是將佈局文件轉化爲View並傳遞給RecyclerView封裝好的ViewHolder View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutResId, parent, false); return new CustomViewHolderHelper(view); } }
RecycerActivity中在調用時能夠這樣使用:
mCustomRecyclerAdapter = new CustomRecyclerAdapter
3 , 在onBindViewHolder方法中也不該該直接爲itemView設置屬性,咱們能夠在這裏記錄itemView的position,並給它設置監聽事件,更重要的咱們這這裏能夠建立一個抽象方法,讓調用者本身去實現業務邏輯。請看代碼:
public abstract class CustomRecyclerAdapter<T> extends RecyclerView.Adapter<CustomViewHolderHelper> implements View.OnClickListener,View.OnLongClickListener{ private Context mContext; private List<T> mData; protected int mLayoutResId; private CustomOnItemClickListener mOnItemClickListener; public CustomRecyclerAdapter(Context context, int layoutResId, List<T> data) { mContext = context; mLayoutResId = layoutResId; mData = data; } /** * Called when RecyclerView needs a new ViewHolder of the given type to represent * an item. * 當 RecyclerView 依據給出的類型須要一個新的 ViewHolder 去展現一個 item 時,該方法將會被調用 * * 這個給出的類型是在 getItemViewType返回的,默認返回 0 。 * * @param parent * @param viewType * @return */ @Override public CustomViewHolderHelper onCreateViewHolder(ViewGroup parent, int viewType) { //onCreateViewHolder方法就是將佈局文件轉化爲View並傳遞給RecyclerView封裝好的ViewHolder View view = LayoutInflater.from(parent.getContext()).inflate(mLayoutResId, parent, false); return new CustomViewHolderHelper(view); } /** * Called by RecyclerView to display the data at the specified position. This method should * update the contents of the ViewHolder#itemView to reflect the item at the given position. * * RecyclerView 將要在特殊的位置上顯示數據時,該方法將被調用。該方法將會在固定的位置上 * 把ViewHolder裏的itemView數據映射在item中。 * @param holder * @param position */ @Override public void onBindViewHolder(CustomViewHolderHelper holder, int position) { //把每個itemView設置一個標籤,方便之後根據標籤獲取到該itemView以便作其餘事項,好比點擊事件 holder.itemView.setTag(position); holder.itemView.setOnClickListener(this); holder.itemView.setOnLongClickListener(this); T itemData = mData.get(position); displayContents(holder,itemData); } /** *用來在holder中設置每一個ItemView的顯示數據 * 設定爲抽象方法,是爲:本身自己並不實現,誰使用誰設置 * @param holder * @param itemData */ protected abstract void displayContents(CustomViewHolderHelper holder, T itemData); }
上面註解也寫的很清楚,相信你們一看就明白,至於爲何能夠直接使用holder.itemView來獲取每一個itemView是由於在onCreateViewHolder()方法中,咱們返回了一個新的對象引用,這個對象的構造方法中使用super(itemView);把咱們的itemView傳遞到了ViewHolder中,請看它的源碼構造方法:
public ViewHolder(View itemView) { if (itemView == null) { throw new IllegalArgumentException("itemView may not be null"); } this.itemView = itemView; }
所以咱們能夠在onBindViewHolder()方法中,直接使用holder.itemView來獲取itemView。那麼接下來,在RecycerActivity中,咱們這樣使用:
mCustomRecyclerAdapter = new CustomRecyclerAdapter<String>(this, R.layout.item_view, mData) { @Override protected void displayContents(CustomViewHolderHelper holder, String itemData) { holder.setText(R.id.tv_item,itemData); } };
固然你必須也得在ViewHolder中定義相應的方法,如:
public class CustomViewHolderHelper extends RecyclerView.ViewHolder{ private SparseArray<View> views; public CustomViewHolderHelper(View itemView) { super(itemView); views = new SparseArray<View>(); } private <T extends View> T converToViewFromId(int resId) { View view = views.get(resId); if(view == null){ view = itemView.findViewById(resId); } views.put(resId,view); return (T)view; } public CustomViewHolderHelper setText(int resId, String value){ TextView itemView = converToViewFromId(resId); if (TextUtils.isEmpty(value)) { itemView.setText(""); } else { itemView.setText(value); } return this; } }
ok,這樣咱們連第四個問題也一併解決了,看下效果吧,徹底的同樣,這樣咱們就實現的重複使用,可是有人會有疑問,這裏也就只能使用TextView啊,其實已經在ViewHolder中給出了答案,你們只須要在添加相對應的方法便可,好比
public CustomViewHolderHelper setImageResource(int viewId, int imageResId) { ImageView view = converToViewFromId(viewId); view.setImageResource(imageResId); return this; } public CustomViewHolderHelper setOnClickListener(int viewId, View.OnClickListener listener) { View view = converToViewFromId(viewId); view.setOnClickListener(listener); return this; }
上面我又添加了兩個方法,用於點擊事件和加載圖片的ImageView,下面咱們再把itemView佈局文件修改下:
item_view.xml佈局文件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_item" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/tv_item" android:layout_toRightOf="@+id/iv_item" android:layout_width="match_parent" android:layout_height="30dp" android:text="I am item view"/> <Button android:id="@+id/btn_item" android:layout_toRightOf="@+id/iv_item" android:layout_below="@+id/tv_item" android:layout_width="wrap_content" android:text="點我" android:layout_height="wrap_content" /> </RelativeLayout>
RecycerActivity中在調用時能夠這樣使用:
mCustomRecyclerAdapter = new CustomRecyclerAdapter<String>(this, R.layout.item_view, mData) { @Override protected void displayContents(CustomViewHolderHelper holder, String itemData) { holder.setText(R.id.tv_item,itemData) .setImageResource(R.id.iv_item,R.mipmap.ic_launcher) .setOnClickListener(R.id.btn_item, new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(RecycerActivity.this,"您單擊了按鈕",Toast.LENGTH_SHORT).show(); } }); } };
ok,來看下效果吧
是否是能夠了呢,這樣咱們就能夠徹底的一次定義N次複用了,每次使用只須要更換不一樣的佈局文件便可而不須要再次編寫代碼,學會了吧。
其實咱們這節課主要講解的RecyclerView.setAdapter的內容,其餘的三個咱們並無詳細的介入,咱們會再之後的博文中陸續的講解。
好了,今天就講到這裏吧,祝你們學習愉快。
更多資訊請關注微信平臺,有博客更新會及時通知。愛學習愛技術。