前言
recyclerView是Android 5.0 materials design 中的組件之一,是一個用來取代listView的SDK,它的靈活性與可替代性比listview更好,原理和listView相似,都是僅僅維護少量的view來加載展示大量的數據集。下面就開始對它進行介紹。介紹之前,先將實現效果展示出來:
使用recyclerView的好處:
recyclerView封裝了viewHolder的回收復用機制,也就是說recyclerView標準化了viewHolder,編寫Adapter面向的是viewHolder而不再是view了,復用的邏輯被封裝了,寫起來也就更方便了。recyclerView提供了一種插拔式的體驗,高度的解耦,異常的靈活,針對一個Item的顯示RecylerView專門抽取出了相應的類,來控制Item的顯示,使其的擴展性非常強。它還可以實現多種效果,例如你想要橫向或者縱向的控制滑動列表,可以通過LinearLayoutManager這個類去控制,類似于GridView的展示效果可以用GridLayoutManager這個類,瀑布流的效果可以通過staggeredGridLayoutManager實現,另外你想控制Item的分隔線,可以通過繼承RecylerView的ItemDecoration這個類,控制Item增刪的動畫,可以通過ItemAnimator這個類進行控制,然后針對自己的業務需求去抒寫代碼。
recyclerView的簡單使用:
-
添加依賴:
dependencies { ... compile 'com.android.support:recyclerview-v7:21.0.+' }
2.在xml布局文件中創建一個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>
創建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,設置他的顯示樣式,并為他設置Adapter.
mRecyclerView = (RecyclerView) findViewById(R.id.recycle_view); //創建默認的線性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); //設置適配器 mAdapter = new MyAdapter(this); mRecyclerView.setAdapter(mAdapter); //也可以設置分割線 recyclerView.addItemDecoration(new DividerGridItemDecoration(this )); //設置增加或刪除條目的動畫 recyclerView.setItemAnimator( new DefaultItemAnimator());
為recyclerView編寫Adapter:
/**
* 創建adapter需要實現三個方法:
* 1. onCreateViewHolder():這個方法主要生成為每個Item inflater出一個View,但是該方法返回的是一個ViewHolder。該方法把View直接封裝在ViewHolder中,然后我們面向的是ViewHolder這個實例,當然這個ViewHolder需要我們自己去編寫,直接省去了當初的convertView.setTag(holder)和convertView.getTag()這些繁瑣的步驟。
* 2. onBindViewHolder():這個方法主要用于適配渲染數據到View中。將界面與數據進行綁定,方法提供給你了一個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;
}
/**
* 設置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來實現界面的數據加載就實現了。
RecyclerView的進階
給RecyclerView的item添加點擊事件:
1.由于RecyclerView沒有提供直接添加item點擊事件的方法,因此需要通過自定義接口實現item的點擊事件。
/**
* 自定義接口,給item設置點擊事件
*/
//聲明接口的變量
private onRecycleViewItemClickListener mOnItemClickListener = null;
public interface onRecycleViewItemClickListener {
void onItemClick(View view, int position);
}
public void setOnItemClickListener(onRecycleViewItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
2.在MyAdapter類中,通過實現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的實現方法:onCreateViewHolder()中給view添加點擊事件,onBindViewHolder()通過setTag的方式設置item的position。
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
view = View.inflate(parent.getContext(), R.layout.item_adapter, null);
//給新創建的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設置暴露出來的setOnItemClickListener()方法,來響應點擊事件。
//給item設置點擊事件
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()實現。
//添加分割線
mRecyclerView.addItemDecoration(new RecycleViewItemDecoration());
2.自定義類RecycleViewItemDecoration繼承RecyclerView.ItemDecoration,并實現它的兩個方法getItemOffsets()和onDraw()方法。
/**
* 設置item的分割線
*/
private class RecycleViewItemDecoration extends RecyclerView.ItemDecoration {
private Paint mPaint;
public RecycleViewItemDecoration() {
//構建畫筆
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.GRAY);
}
/**
* 實現兩個方法
* 確定分割線的位置
* @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();
//指定繪畫的矩形區域
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添加頭部和底部有具體的方法實現,通過addHeaderView()和addFooterView()兩個方法就能實現。而recyclerView沒有提供具體的方法實現,需要自定義來實現。通過對listview添加頭部的方法addHeaderView()的源碼進行分析,發現它是在對Adapter進行判空處理之后,判斷當前的listview是否已經添加Adapter,然后在原來的adapter的基礎上進行了一層包裝,其實listview本身也是不支持添加頭部和底部的,只是系統為我們設定好了。最主要是需要 清楚HeaderViewListAdapter是如何實現的。
public void addHeaderView(View v, Object data, boolean isSelectable) {
//第一步 數據的初始化和賦值 并添加到頭部視圖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) {
//第二步 關鍵點
//給mAdapter賦值成HeaderViewListAdapter對象 并把head、foot視圖列表和原來的adapter傳進去
//所以這里就是關鍵 從這里進入看HeaderViewListAdapter類的源碼
if (!(mAdapter instanceof HeaderViewListAdapter)) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
//第三步 被觀察者發出修改通知
// 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();
}
}
}
接下來,就自己實現一下,實現的基本原理是通過getItemViewType()方法返回不同的類型來添加頭部和底部。
貼上實現該功能的整段代碼,對上面的代碼塊有部分改動:
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個數
private int mHeaderCount = 1;
//底部View個數
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[] = {"瀏覽器", "輸入法", "健康", "效率", "教育", "理財",
"閱讀", "個性化", "購物", "資訊", "生活", "工具", "出行", "通訊", "拍照", "社交",
"影音", "安全", "休閑", "棋牌", "益智", "射擊", "體育", "兒童", "網游", "角色", "策略",
"經營", "競速"};
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;
}
//填充內容的長度
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 {
//內容View
return ITEM_TYPE_CONTENT;
}
}
/**
* 這兩個方法是用來判斷當前item是頭部還是底部,在使用gridLayoutManager的時候調用
* @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設置點擊事件
*/
//聲明接口的變量
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());
}
}
/**
* 設置viewHolder
*/
//中間內容的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類型列表在設置Adapter后需要調用這個方法mLayoutManager.setSpanSizeLookup(),根據當前Item類型來判斷占據的橫向格數。
/**
* 如果RecyclerView使用Grid類型列表在設置Adapter后需要調用這個方法,
* 根據當前Item類型來判斷占據的橫向格數
*/
mLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (mAdapter.isHeaderView(position) ||
mAdapter.isBottomView(position)) ? mLayoutManager.getSpanCount() : 1;
}
});
RecyclerView實現列表的上拉加載和下拉刷新:
要實現列表的下拉刷新可以使用Android提供的swipeRefreshLayout來實現下拉刷新或者上拉加載,也可以自定義上拉加載和下拉刷新,
使用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中的代碼實現,需要先找到控件,然后刷新的監聽方法,該處使用了handler來模擬網絡請求操作。
//利用swipeRefreshLayout設置下拉刷新
private void setPullToRefresh() {
//設置圈圈的顏色
mRefreshLayout.setColorSchemeColors(Color.RED,Color.BLUE,Color.BLACK);
//設置圈圈的顏色,里面放的是顏色的資源值
// 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) 設置下拉監聽,當用戶下拉的時候會去執行回調
2)setColorSchemeColors(int... colors) 設置 進度條的顏色變化,最多可以設置4種顏色
3)setProgressViewOffset(boolean scale, int start, int end) 調整進度條距離屏幕頂部的距離
4)setRefreshing(boolean refreshing) 設置SwipeRefreshLayout當前是否處于刷新狀態,一般是在請求數據的時候設置為true,在數據被加載到View中后,設置為false。
總結:
到此關于RecyclerView的一些常見使用方式就介紹完了,總的來說RecyclerView的使用比起listview還是要便捷的多。