Android開發(fā)中RecyclerView組件使用的一些進階技講解
RecyclerView的優(yōu)勢:
- 它自帶ViewHolder來實現(xiàn)View的復(fù)用機制,再也不用ListView那樣在getView()里自己寫了
- 使用LayoutManager可以實現(xiàn)ListView,GridView以及流式布局的列表效果
- 通過setItemAnimator(ItemAnimator animator)可以實現(xiàn)增刪動畫(懶的話,可以使用默認的ItemAnimator對象,效果也不錯)
- 控制item的間隔,可以使用addItemDecoration(ItemDecoration decor),不過里邊的ItemDecoration是一個抽象類,需要自己去實現(xiàn)...
用法介紹:
導入RecyclerView的v7庫:
RecyclerView是一個android.support.v7庫里的控件,因此在使用的時候我們需要在gradle配置文件里加上compile 'com.android.support:recyclerview-v7:22.2.1'來引入google官方的這個庫
xml布局中,使用常規(guī)的控件引入方式,來引入RecyclerView,如下:
<android.support.v7.widget.RecyclerView android:id="@+id/recyclerview_content" style="?recyclerview_style" android:scrollbars="vertical" android:fadeScrollbars="true" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginBottom="-55dp" />
代碼中的寫法基本和ListView相差無幾,但還是要重點說一下:
在實例化RecyclerView之后,我們需要使用setLayoutManager()給它設(shè)置布局管理器,其中的實參即就是LayoutManager,這里總共有兩種LayoutManager:
1.StaggeredGridLayoutManager,是我們之前提到的流式布局:
它有一個構(gòu)造方法StaggeredGridLayoutManager(int spanCount, int orientation),第一個是網(wǎng)格的列數(shù),第二個參數(shù)是數(shù)據(jù)呈現(xiàn)的方向
(如果是豎直,那么第一個參數(shù)的意義就是列數(shù),反之為行數(shù)。而且第二個參數(shù)在StaggeredGridLayoutManager里也有同樣名稱的常量,請同學們自行采納[這里還是建議大家使用庫里自帶的常量,因為他們一般都是整型值,這樣可以避免各個類里所判別的常量值不一樣而導致的其他問題]);
2.GridLayoutManager,網(wǎng)格布局(流式布局應(yīng)該是它的一個特殊情況):
GridLayoutManager(Context context, int spanCount)或
GridLayoutManager(Context context, int spanCount, int orientation,
boolean reverseLayout)需要說明的是,最后一個參數(shù)表示的是是否逆向布局(意思是將數(shù)據(jù)反向顯示,原先從左向右,從上至下。設(shè)為true之后全部逆轉(zhuǎn))。
小提示:在這兩個LayoutManager中,默認的orientation為vertical,reverseLayout為false。對應(yīng)的參數(shù)在GridLayoutManager中都有對應(yīng)的方法來進行補充設(shè)置。而在StaggeredGridLayoutManager中所有的方法都針對reverseLayout做了判斷,然而它并沒有給出這個參數(shù)設(shè)定值的api。
線性布局, LinearLayoutManager
LinearLayoutManager(Context context)構(gòu)建一個默認布局方向為VERTICAL的RecyclerView,這種樣式也是ListView默認的。
LinearLayoutManager(Context context, int orientation, boolean reverseLayout)。發(fā)現(xiàn)沒有,線性布局和網(wǎng)格布局幾乎是一樣的。只是少了一個spanCount參數(shù)。
和ListView一樣,為RecyclerView設(shè)置Adapter
class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.MyViewHolder> { @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { MyViewHolder holder = new MyViewHolder(LayoutInflater.from( HomeActivity.this).inflate(R.layout.item_home, parent, false)); return holder; } @Override public void onBindViewHolder(MyViewHolder holder, int position) { holder.tv.setText(mDatas.get(position)); } @Override public int getItemCount() { return mDatas.size(); } class MyViewHolder extends ViewHolder { TextView tv; public MyViewHolder(View view) { super(view); tv = (TextView) view.findViewById(R.id.id_num); } } }
如上,即就是Adapter的寫法。其中的ViewHolder就是拿來負責View的回收和復(fù)用的,這樣就不需要我們自己寫完ViewHolder之后,還要在getView(int position, View convertView, ViewGroup parent)里一頓判斷,一頓綁定,一頓find了。而且這里的ViewHolder成為了RecyclerView中必須繼承的一部分,重寫完后就需要放入 RecyclerView.Adapter< >這里來對基類的范型初始化。
在這里,Recyclerview已經(jīng)為你封裝好了:
- getItemCount()就不必多說了,和ListView是一樣的
- getItemViewType(int position)是用來根據(jù)position的不同來實現(xiàn)RecyclerView中對不同布局的要求。從這個方法中所返回的值會在onCreateViewHolder中用到。比如頭部,尾部,等等的特殊itemView(這里說成ViewHolder比較好)都可以在這里進行判斷。
- onCreateViewHolder(ViewGroup parent, int viewType)是用來配合寫好的ViewHolder來返回一個ViewHolder對象。這里也涉及到了條目布局的加載。viewType則表示需要給當前position生成的是哪一種ViewHolder,這個參數(shù)也說明了RecyclerView對多類型ItemView的支持。
- onBindViewHolder(MyViewHolder holder, int position)專門用來綁定ViewHolder里的控件和數(shù)據(jù)源中position位置的數(shù)據(jù)。
這里,會有人問,那么item的子控件findViewById 去哪兒了?我們把它交給了ViewHolder的構(gòu)造方法(其他方法也可以),它的本質(zhì)是在onCreateViewHolder方法里生成ViewHolder的時候執(zhí)行的。
提升:
1.代碼重構(gòu):
在上邊看了這么多,有木有覺得,ViewHolder的功能并不是非常明確?它既負責了子控件的查詢,又負責了子控件的裝載工作。而布局加載和數(shù)據(jù)綁定卻交給了Adapter......
我們來看看掘金的做法:
首先,它把Adapter和ViewHolder的功能以一種較為低耦合的方式進行了職能分離,讓ViewHolder里所有的邏輯代碼全部都只出現(xiàn)在ViewHolder中。我們現(xiàn)在就對前邊提到的代碼進行重構(gòu):
ViewHodler:
class MyViewHolder extends ViewHolder{ TextView tv; public MyViewHolder(Context context, View view){ super(view); tv = (TextView) view.findViewById(R.id.id_num); //當然,我們也可以在這里使用view來對RecyclerView的一個item進行事件監(jiān)聽,也可以使用 //tv等子控件來實現(xiàn)item的子控件的事件監(jiān)聽。這也是我之所以要傳context的原因之一呢~ ...... } public static MyViewHolder newInstance(Activity context, ViewGroup parent){ View view = LayoutInflater.from( context).inflate(R.layout.item_home, parent,false); return new MyViewHolder(context, view); } } //你沒看錯,數(shù)據(jù)綁定也被整合進來了, //將adapter里的數(shù)據(jù)根據(jù)position獲取到后傳進來。當然,也可以根據(jù)具體情況來做調(diào)整。 public void onBinViewHolder(String data){ tv.setText(data);//既然這里也有子控件,那么這里也可以做item子控件的事件監(jiān)聽嘍 }
RecyclerView:
class HomeAdapter extends RecyclerView.Adapter<MyViewHolder>{ @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType){//如需要,還要對viewType做判斷 return MyViewHolder.newInstance(this, parent) } @Override public void onBindViewHolder(MyViewHolder holder, int position){ holder.onBindViewHolder(mDatas.get(position)); } @Override public int getItemCount(){ return mDatas.size(); } }
抽取一個條目點擊事件,讓它更像ListView:
class HomeAdapter extends RecyclerView.Adapter<MyViewHolder>{ private OnItemClickListener mOnItemClickListener; public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener) { this.mOnItemClickLitener = mOnItemClickLitener; } @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType){//如需要,還要對viewType做判斷 return MyViewHolder.newInstance(this, parent) } @Override public void onBindViewHolder(MyViewHolder holder, int position){ holder.onBindViewHolder(mDatas.get(position)); //如果設(shè)置了回調(diào),則設(shè)置點擊事件 if (mOnItemClickLitener != null) { viewHolder.itemView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mOnItemClickLitener.onItemClick(viewHolder.itemView, i); } }); } } @Override public int getItemCount(){ return mDatas.size(); } public interface OnItemClickLitener { void onItemClick(View view, int position); } }
接口調(diào)用
mAdapter.setOnItemClickLitener(new OnItemClickLitener() { @Override public void onItemClick(View view, int position) { ...... } });
2.External
上邊提到了
控制item的間隔,可以使用addItemDecoration(ItemDecoration decor),不過里邊的ItemDecoration是一個抽象類,需要自己去實現(xiàn)...
這個問題,那我們來實際解決一下:
這是羊神實現(xiàn)的一個子類
package com.zhy.sample.demo_recyclerview; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.State; import android.util.Log; import android.view.View; public class DividerItemDecoration 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 DividerItemDecoration(Context context, int orientation) { final TypedArray a = context.obtainStyledAttributes(ATTRS); mDivider = a.getDrawable(0); a.recycle(); 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) { Log.v("recyclerview - itemdecoration", "onDraw()"); if (mOrientation == VERTICAL_LIST) { drawVertical(c, parent); } else { drawHorizontal(c, parent); } } public 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); android.support.v7.widget.RecyclerView v = new android.support.v7.widget.RecyclerView(parent.getContext()); final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams(); final int top = child.getBottom() + params.bottomMargin; final int bottom = top + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } public 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; final int right = left + mDivider.getIntrinsicHeight(); mDivider.setBounds(left, top, right, bottom); mDivider.draw(c); } } @Override public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) { if (mOrientation == VERTICAL_LIST) { outRect.set(0, 0, 0, mDivider.getIntrinsicHeight()); } else { outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0); } } }
然后就是為我們的RecyclerView實例添加分割線
addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
系統(tǒng)默認的分割線往往達不到我們產(chǎn)品和設(shè)計師的要求,怎么辦呢?
<item name="android:listDivider">@drawable/your_custom_divider</item>
通過以上xml屬性,將系統(tǒng)的listDivider設(shè)為自己畫出來的分割線,只需要放在你對應(yīng)activity的主題下即可。
技巧:RecyclerView 滾動條的顯示與隱藏
<android.support.v7.widget.RecyclerView android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="vertical" > </android.support.v7.widget.RecyclerView>
縱向顯示:
android:scrollbars="vertical"
橫向顯示:
android:scrollbars="horizontal"
隱藏:
android:scrollbars="none"
- Android RecyclerView 基礎(chǔ)知識詳解
- Android RecyclerView加載不同布局簡單實現(xiàn)
- Android添加圖片到ListView或者RecyclerView顯示
- Android中RecyclerView實現(xiàn)橫向滑動代碼
- Android RecyclerView詳解之實現(xiàn) ListView GridView瀑布流效果
- Android中RecyclerView點擊Item設(shè)置事件
- Android RecyclerView藝術(shù)般的控件使用完全解析
- Android代碼實現(xiàn)AdapterViews和RecyclerView無限滾動
- Android RecyclerView滑動刪除和拖動排序
- Android RecyclerView的Item自定義動畫及DefaultItemAnimator源碼分析
- 學習Android開發(fā)之RecyclerView使用初探
- Android RecyclerView 數(shù)據(jù)綁定實例代碼
相關(guān)文章
Android編程實現(xiàn)根據(jù)經(jīng)緯度查詢地址并對獲取的json數(shù)據(jù)進行解析的方法
這篇文章主要介紹了Android編程實現(xiàn)根據(jù)經(jīng)緯度查詢地址并對獲取的json數(shù)據(jù)進行解析的方法,結(jié)合實例形式分析了Android的經(jīng)緯度地址解析與json格式數(shù)據(jù)操作相關(guān)技巧,需要的朋友可以參考下2017-02-02Android程序報錯程序包org.apache.http不存在問題的解決方法
這篇文章主要介紹了Android程序報錯"程序包org.apache.http不存在——Android 6.0已經(jīng)不支持HttpClient" 問題的解決方法,感興趣的小伙伴們可以參考一下2016-06-06Android利用Intent.ACTION_SEND進行分享
這篇文章主要介紹了Android利用Intent.ACTION_SEND進行分享,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05Android開發(fā)之日歷CalendarView用法示例
這篇文章主要介紹了Android開發(fā)之日歷CalendarView用法,簡單分析了日歷CalendarView組件的功能、屬性設(shè)置方法、界面布局、事件監(jiān)聽等相關(guān)操作技巧,需要的朋友可以參考下2019-03-03Android開發(fā)實現(xiàn)實時檢測藍牙連接狀態(tài)的方法【附源碼下載】
這篇文章主要介紹了Android開發(fā)實現(xiàn)實時檢測藍牙連接狀態(tài)的方法,涉及Android針對藍牙連接狀態(tài)的監(jiān)測操作相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2017-11-11