前言
recyclerView是Android 5.0 materials design 中的組件之一,是一個用來取代listView的SDK,它的靈活性與可替代性比listview更好,原理和listView相似,都是僅僅維護少量的view來加載展示大量的數(shù)據(jù)集。下面就開始對它進行介紹。介紹之前,先將實現(xiàn)效果展示出來:
使用recyclerView的好處:
recyclerView封裝了viewHolder的回收復用機制,也就是說recyclerView標準化了viewHolder,編寫Adapter面向的是viewHolder而不再是view了,復用的邏輯被封裝了,寫起來也就更方便了。recyclerView提供了一種插拔式的體驗,高度的解耦,異常的靈活,針對一個Item的顯示RecylerView專門抽取出了相應(yīng)的類,來控制Item的顯示,使其的擴展性非常強。它還可以實現(xiàn)多種效果,例如你想要橫向或者縱向的控制滑動列表,可以通過LinearLayoutManager這個類去控制,類似于GridView的展示效果可以用GridLayoutManager這個類,瀑布流的效果可以通過staggeredGridLayoutManager實現(xiàn),另外你想控制Item的分隔線,可以通過繼承RecylerView的ItemDecoration這個類,控制Item增刪的動畫,可以通過ItemAnimator這個類進行控制,然后針對自己的業(yè)務(wù)需求去抒寫代碼。
recyclerView的簡單使用:
-
添加依賴:
dependencies { ... compile 'com.android.support:recyclerview-v7:21.0.+' }
2.在xml布局文件中創(chuàng)建一個RecyclerView的布局和item的布局:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"/>
</RelativeLayout>
創(chuàng)建item布局,通過使用CardView進行包裹達到更好的界面效果展示:
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="10dp"
android:layout_margin="5dp"
android:foreground="?android:attr/selectableItemBackground"
app:cardElevation="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dp">
<ImageView
android:id="@+id/item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher" />
<TextView
android:id="@+id/item_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="10dp"
android:text="標題"
android:textSize="18sp" />
</LinearLayout>
</android.support.v7.widget.CardView>
-
編寫代碼邏輯,在MainActivity中找到recyclerView,設(shè)置他的顯示樣式,并為他設(shè)置Adapter.
mRecyclerView = (RecyclerView) findViewById(R.id.recycle_view); //創(chuàng)建默認的線性LinearLayoutManager LinearLayoutManager mLayoutManager = new LinearLayoutManager(this); //gridLayoutManager 多列表 GridLayoutManager gridLayoutManager = new GridLayoutManager(this,3); //StaggeredGridLayoutManager 瀑布流 StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(5,StaggeredGridLayoutManager.HORIZONTAL); mRecyclerView.setLayoutManager(mLayoutManager); //確定每個item的高度都是固定的,提高性能 mRecyclerView.setHasFixedSize(true); //設(shè)置適配器 mAdapter = new MyAdapter(this); mRecyclerView.setAdapter(mAdapter); //也可以設(shè)置分割線 recyclerView.addItemDecoration(new DividerGridItemDecoration(this )); //設(shè)置增加或刪除條目的動畫 recyclerView.setItemAnimator( new DefaultItemAnimator());
為recyclerView編寫Adapter:
/**
* 創(chuàng)建adapter需要實現(xiàn)三個方法:
* 1. onCreateViewHolder():這個方法主要生成為每個Item inflater出一個View,但是該方法返回的是一個ViewHolder。該方法把View直接封裝在ViewHolder中,然后我們面向的是ViewHolder這個實例,當然這個ViewHolder需要我們自己去編寫,直接省去了當初的convertView.setTag(holder)和convertView.getTag()這些繁瑣的步驟。
* 2. onBindViewHolder():這個方法主要用于適配渲染數(shù)據(jù)到View中。將界面與數(shù)據(jù)進行綁定,方法提供給你了一個viewHolder,而不是原來的convertView。
* 3. getItemCount():這個方法就類似于BaseAdapter的getCount方法了,即總共有多少個條目。
*/
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private View view;
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
view = View.inflate(parent.getContext(), R.layout.item_adapter, null);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.setData(position);
}
@Override
public int getItemCount() {
return 1000;
}
/**
* 設(shè)置viewHolder
*/
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvName;
ImageView ivImage;
public MyViewHolder(View itemView) {
super(itemView);
//初始化控件
tvName = (TextView) view.findViewById(R.id.item_textview);
ivImage = (ImageView) view.findViewById(R.id.item_image);
}
}
}
- 這樣一個通過RecyclerView來實現(xiàn)界面的數(shù)據(jù)加載就實現(xiàn)了。
RecyclerView的進階
給RecyclerView的item添加點擊事件:
1.由于RecyclerView沒有提供直接添加item點擊事件的方法,因此需要通過自定義接口實現(xiàn)item的點擊事件。
/**
* 自定義接口,給item設(shè)置點擊事件
*/
//聲明接口的變量
private onRecycleViewItemClickListener mOnItemClickListener = null;
public interface onRecycleViewItemClickListener {
void onItemClick(View view, int position);
}
public void setOnItemClickListener(onRecycleViewItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
2.在MyAdapter類中,通過實現(xiàn)view的onClickListener方法,重寫它的onClick()方法;
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> implements View.OnClickListener {
....
@Override
public void onClick(View view) {
//在該處先判空。
if (mOnItemClickListener != null) {
//通過getTag的方式獲取到position
mOnItemClickListener.onItemClick(view, (Integer) view.getTag());
}
}
3.在Adapter的實現(xiàn)方法:onCreateViewHolder()中給view添加點擊事件,onBindViewHolder()通過setTag的方式設(shè)置item的position。
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
view = View.inflate(parent.getContext(), R.layout.item_adapter, null);
//給新創(chuàng)建的view注冊點擊事件
view.setOnClickListener(this);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.setData(position);
//將position保存在viewItem的tag中,以便在點擊的時候獲取。
holder.itemView.setTag(position);
}
4.在MainActivity中,通過給adapter設(shè)置暴露出來的setOnItemClickListener()方法,來響應(yīng)點擊事件。
//給item設(shè)置點擊事件
mAdapter.setOnItemClickListener(new MyAdapter.onRecycleViewItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this,"第"+position+"個item被點擊了",Toast.LENGTH_SHORT).show();
}
});
給recyclerView的item添加分割線:
recyclerView有提供添加分割線的方法:通過addItemDecoration()實現(xiàn)。
//添加分割線
mRecyclerView.addItemDecoration(new RecycleViewItemDecoration());
2.自定義類RecycleViewItemDecoration繼承RecyclerView.ItemDecoration,并實現(xiàn)它的兩個方法getItemOffsets()和onDraw()方法。
/**
* 設(shè)置item的分割線
*/
private class RecycleViewItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
public RecycleViewItemDecoration() {
//構(gòu)建畫筆
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
}
/**
* 實現(xiàn)兩個方法
* 確定分割線的位置
* @param outRect
* @param view
* @param parent
* @param state
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
//先確定位置,在每個item的底部留出5px的位置
int position = parent.getChildAdapterPosition(view);
//判斷當前item不是第0個位置,就在item的頂部繪制分割線
if(position != 0) {
outRect.top = 5;
}
}
/**
* 繪制分割線,用Canvas在每一個item的頭部繪制
* @param c
* @param parent
* @param state
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
int childCount = parent.getChildCount();
//指定繪畫的矩形區(qū)域
Rect rect = new Rect();
rect.left = parent.getPaddingLeft();
rect.right = parent.getWidth() - parent.getPaddingRight();
for (int i = 1; i < childCount; i++) {
//分割線的底部是itemView的頂部
rect.bottom = parent.getChildAt(i).getTop();
rect.top = rect.bottom - 5;
c.drawRect(rect,mPaint);
}
}
}
給RecyclerView添加頭部和底部:
listview添加頭部和底部有具體的方法實現(xiàn),通過addHeaderView()和addFooterView()兩個方法就能實現(xiàn)。而recyclerView沒有提供具體的方法實現(xiàn),需要自定義來實現(xiàn)。通過對listview添加頭部的方法addHeaderView()的源碼進行分析,發(fā)現(xiàn)它是在對Adapter進行判空處理之后,判斷當前的listview是否已經(jīng)添加Adapter,然后在原來的adapter的基礎(chǔ)上進行了一層包裝,其實listview本身也是不支持添加頭部和底部的,只是系統(tǒng)為我們設(shè)定好了。最主要是需要 清楚HeaderViewListAdapter是如何實現(xiàn)的。
public void addHeaderView(View v, Object data, boolean isSelectable) {
//第一步 數(shù)據(jù)的初始化和賦值 并添加到頭部視圖list中
final FixedViewInfo info = new FixedViewInfo();
info.view = v;
info.data = data;
info.isSelectable = isSelectable;
//添加頭部集合
mHeaderViewInfos.add(info);
mAreAllItemsSelectable &= isSelectable;
// Wrap the adapter if it wasn't already wrapped.
if (mAdapter != null) {
//第二步 關(guān)鍵點
//給mAdapter賦值成HeaderViewListAdapter對象 并把head、foot視圖列表和原來的adapter傳進去
//所以這里就是關(guān)鍵 從這里進入看HeaderViewListAdapter類的源碼
if (!(mAdapter instanceof HeaderViewListAdapter)) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
//第三步 被觀察者發(fā)出修改通知
// In the case of re-adding a header view, or adding one later on,
// we need to notify the observer.
if (mDataSetObserver != null) {
mDataSetObserver.onChanged();
}
}
}
接下來,就自己實現(xiàn)一下,實現(xiàn)的基本原理是通過getItemViewType()方法返回不同的類型來添加頭部和底部。
貼上實現(xiàn)該功能的整段代碼,對上面的代碼塊有部分改動:
public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements View.OnClickListener {
private View view;
private Context mContext;
private LayoutInflater mLayoutInflater;
//item類型
public static final int ITEM_TYPE_HEADER = 0;
public static final int ITEM_TYPE_CONTENT = 1;
public static final int ITEM_TYPE_BOTTOM = 2;
//頭部View個數(shù)
private int mHeaderCount = 1;
//底部View個數(shù)
private int mBottomCount = 1;
int icons[] = {R.drawable.g1, R.drawable.g2, R.drawable.g3, R.drawable.g4, R.drawable.g5, R.drawable.g6, R.drawable.g7, R.drawable.g9,
R.drawable.g10, R.drawable.g11, R.drawable.g12, R.drawable.g13, R.drawable.g14, R.drawable.g15, R.drawable.g16, R.drawable.g17, R.drawable.g18, R.drawable.g19,
R.drawable.g20, R.drawable.g21, R.drawable.g22, R.drawable.g23, R.drawable.g24, R.drawable.g25, R.drawable.g26, R.drawable.g27, R.drawable.g28, R.drawable.g29,};
String names[] = {"瀏覽器", "輸入法", "健康", "效率", "教育", "理財",
"閱讀", "個性化", "購物", "資訊", "生活", "工具", "出行", "通訊", "拍照", "社交",
"影音", "安全", "休閑", "棋牌", "益智", "射擊", "體育", "兒童", "網(wǎng)游", "角色", "策略",
"經(jīng)營", "競速"};
public MyAdapter(Context context) {
this.mContext = context;
mLayoutInflater = LayoutInflater.from(context);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ITEM_TYPE_HEADER) {
return new HeaderViewHolder(mLayoutInflater.inflate(R.layout.rv_headerview, parent, false));
} else if (viewType == mHeaderCount) {
view = mLayoutInflater.inflate(R.layout.item_adapter, parent, false);
view.setOnClickListener(this);
return new MyViewHolder(view);
} else if (viewType == ITEM_TYPE_BOTTOM) {
return new BottomViewHolder(mLayoutInflater.inflate(R.layout.rv_footerview, parent, false));
}
return null;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof HeaderViewHolder) {
} else if (holder instanceof MyViewHolder) {
((MyViewHolder) holder).setData(position);
//將position保存在viewItem的tag中,以便在點擊的時候獲取。
holder.itemView.setTag(position);
} else if (holder instanceof BottomViewHolder) {
}
}
@Override
public int getItemCount() {
return getContentItemCount() + mHeaderCount + mBottomCount;
}
//填充內(nèi)容的長度
public int getContentItemCount() {
return names.length;
}
//判斷當前item類型
@Override
public int getItemViewType(int position) {
int dataItemCount = getContentItemCount();
if (mHeaderCount != 0 && position < mHeaderCount) {
//頭部View
return ITEM_TYPE_HEADER;
} else if (mBottomCount != 0 && position >= (mHeaderCount + dataItemCount)) {
//底部View
return ITEM_TYPE_BOTTOM;
} else {
//內(nèi)容View
return ITEM_TYPE_CONTENT;
}
}
/**
* 這兩個方法是用來判斷當前item是頭部還是底部,在使用gridLayoutManager的時候調(diào)用
* @param position
* @return
*/
public boolean isHeaderView(int position) {
return mHeaderCount != 0 && position < mHeaderCount;
}
public boolean isBottomView(int position) {
return mBottomCount != 0 && position >= (mHeaderCount + getContentItemCount());
}
/**
* 自定義接口,給item設(shè)置點擊事件
*/
//聲明接口的變量
private onRecycleViewItemClickListener mOnItemClickListener = null;
public interface onRecycleViewItemClickListener {
void onItemClick(View view, int position);
}
public void setOnItemClickListener(onRecycleViewItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
@Override
public void onClick(View view) {
if (mOnItemClickListener != null) {
//通過getTag的方式獲取到position
mOnItemClickListener.onItemClick(view, (Integer) view.getTag());
}
}
/**
* 設(shè)置viewHolder
*/
//中間內(nèi)容的viewHolder
public class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvName;
ImageView ivImage;
public MyViewHolder(View itemView) {
super(itemView);
//初始化控件
tvName = (TextView) view.findViewById(R.id.item_textview);
ivImage = (ImageView) view.findViewById(R.id.item_image);
}
public void setData(int position) {
//給控件賦值
tvName.setText(names[position % names.length]);
ivImage.setImageDrawable(mContext.getResources().getDrawable(icons[position % icons.length]));
}
}
//頭部的ViewHolder
public static class HeaderViewHolder extends RecyclerView.ViewHolder {
public HeaderViewHolder(View itemView) {
super(itemView);
}
}
//底部的ViewHolder
public static class BottomViewHolder extends RecyclerView.ViewHolder {
public BottomViewHolder(View itemView) {
super(itemView);
}
}
}
在activity當中如果RecyclerView使用Grid類型列表在設(shè)置Adapter后需要調(diào)用這個方法mLayoutManager.setSpanSizeLookup(),根據(jù)當前Item類型來判斷占據(jù)的橫向格數(shù)。
/**
* 如果RecyclerView使用Grid類型列表在設(shè)置Adapter后需要調(diào)用這個方法,
* 根據(jù)當前Item類型來判斷占據(jù)的橫向格數(shù)
*/
mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (mAdapter.isHeaderView(position) ||
mAdapter.isBottomView(position)) ? mLayoutManager.getSpanCount() : 1;
}
});
RecyclerView實現(xiàn)列表的上拉加載和下拉刷新:
要實現(xiàn)列表的下拉刷新可以使用Android提供的swipeRefreshLayout來實現(xiàn)下拉刷新或者上拉加載,也可以自定義上拉加載和下拉刷新,
使用swipeRefreshLayout來進行控件的下拉刷新。需要在布局文件中對RecyclerView進行包裹。
<android.support.v4.widget.SwipeRefreshLayout
android:id="@+id/srf_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycle_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical">
</android.support.v7.widget.RecyclerView>
</android.support.v4.widget.SwipeRefreshLayout>
接下來就是activity中的代碼實現(xiàn),需要先找到控件,然后刷新的監(jiān)聽方法,該處使用了handler來模擬網(wǎng)絡(luò)請求操作。
//利用swipeRefreshLayout設(shè)置下拉刷新
private void setPullToRefresh() {
//設(shè)置圈圈的顏色
mRefreshLayout.setColorSchemeColors(Color.RED,Color.BLUE,Color.BLACK);
//設(shè)置圈圈的顏色,里面放的是顏色的資源值
// refreshLayout.setColorSchemeColors(R.color.cardview_dark_background,R.color.cardview_light_background);
mRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
Toast.makeText(MainActivity.this,"正在拼命加載中...",Toast.LENGTH_SHORT).show();
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
//停止刷新
mRefreshLayout.setRefreshing(false);
}
},3000);
}
});
}
SwipeRefreshLayout里面需要注意的Api:
1)setOnRefreshListener(OnRefreshListener listener) 設(shè)置下拉監(jiān)聽,當用戶下拉的時候會去執(zhí)行回調(diào)
2)setColorSchemeColors(int... colors) 設(shè)置 進度條的顏色變化,最多可以設(shè)置4種顏色
3)setProgressViewOffset(boolean scale, int start, int end) 調(diào)整進度條距離屏幕頂部的距離
4)setRefreshing(boolean refreshing) 設(shè)置SwipeRefreshLayout當前是否處于刷新狀態(tài),一般是在請求數(shù)據(jù)的時候設(shè)置為true,在數(shù)據(jù)被加載到View中后,設(shè)置為false。
總結(jié):
到此關(guān)于RecyclerView的一些常見使用方式就介紹完了,總的來說RecyclerView的使用比起listview還是要便捷的多。