在android.support.v7包中,提供了一個新控件RecyclerView,是Android 5.0 materials design中的元件之一,用來替換ListView、GridView。本文主要講解RecycleView的基本用法,以及如何配合SwipeRefreshLayout實作下拉重新整理,上拉加載的功能。SwipeRefreshLayout是android.support.v4包中提供的。
一、RecyclerView的基本介紹
//設定擴充卡
m_recyclerView.setAdapter(m_adapter);
//設定布局方式
m_recyclerView.setLayoutManager(m_layoutManager);
//設定分割線
m_recyclerView.addItemDecoration(decoration);
//設定Item切換的動畫
m_recyclerView.setItemAnimator(new DefaultItemAnimator());
這是RecycleView使用的四個基本步驟,看起來是比ListView麻煩點,但是它的功能更加強大。
1、RecycleView.LayoutManager
LinearLayoutManager 相當于ListView;
GridLayoutManager 相當于GridView;
StaggeredGridLayoutManager 相當于瀑布流。
這三種LayoutManager都有含有orientation參數的構造方法,這個參數用來判斷RecycleView是豎直的還是水
平的。所有用RecycleView來實作水準的ListView和GridView更加友善。
2、RecycleView.ItemDecoration
相對于ListView設定分割線這方面而言,RecycleView沒有提供分割線,但是提供了ItemDecoration這個抽象
類給我們自定義分割線的樣式。
以下提供幾種分割線的實作方式:
(1)設定每個Item與周邊的邊距,RecycleView的父布局的顔色就成了分割線的顔色。(以上一種LayoutManager
通用)
public class SpacesItemDecoration extends RecyclerView.ItemDecoration {
private int space;
public SpacesItemDecoration(int space) {
this.space = space;
}
/**
* 設定每個Item與周邊的間距
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
outRect.left = space;
outRect.right = space;
outRect.bottom = space;
if (parent.getChildAdapterPosition(view) == 0 || parent.getChildAdapterPosition(view) == 2 || parent.getChildAdapterPosition(view) == 1) {
outRect.top = space;
}
}
}
(2)可以看到通過讀取系統主題中的 android.R.attr.listDivider作為Item間的分割線,并且支援橫向和縱向。以下是LinearLayoutManager作為LayoutManager時的方式。
public class DividerListItemDecoration 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 DividerListItemDecoration(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) {
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);
}
}
}
(3)GridLayoutManager作為LayoutManager時,分割線的處理。
public class DividerGridItemDecoration extends RecyclerView.ItemDecoration
{
private static final int[] ATTRS = new int[] { android.R.attr.listDivider };
private Drawable mDivider;
public DividerGridItemDecoration(Context context)
{
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
{
drawHorizontal(c, parent);
drawVertical(c, parent);
}
private int getSpanCount(RecyclerView parent)
{
// 列數
int spanCount = -1;
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
{
spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
} else if (layoutManager instanceof StaggeredGridLayoutManager)
{
spanCount = ((StaggeredGridLayoutManager) layoutManager)
.getSpanCount();
}
return spanCount;
}
public void drawHorizontal(Canvas c, RecyclerView parent)
{
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.getLeft() - params.leftMargin;
final int right = child.getRight() + params.rightMargin
+ mDivider.getIntrinsicWidth();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public void drawVertical(Canvas c, RecyclerView parent)
{
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.getTop() - params.topMargin;
final int bottom = child.getBottom() + params.bottomMargin;
final int left = child.getRight() + params.rightMargin;
final int right = left + mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private boolean isLastColum(RecyclerView parent, int pos, int spanCount,
int childCount)
{
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
{
if ((pos + 1) % spanCount == 0)// 如果是最後一列,則不需要繪制右邊
{
return true;
}
} else if (layoutManager instanceof StaggeredGridLayoutManager)
{
int orientation = ((StaggeredGridLayoutManager) layoutManager)
.getOrientation();
if (orientation == StaggeredGridLayoutManager.VERTICAL)
{
if ((pos + 1) % spanCount == 0)// 如果是最後一列,則不需要繪制右邊
{
return true;
}
} else
{
childCount = childCount - childCount % spanCount;
if (pos >= childCount)// 如果是最後一列,則不需要繪制右邊
return true;
}
}
return false;
}
private boolean isLastRaw(RecyclerView parent, int pos, int spanCount,
int childCount)
{
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager)
{
childCount = childCount - childCount % spanCount;
if (pos >= childCount)// 如果是最後一行,則不需要繪制底部
return true;
} else if (layoutManager instanceof StaggeredGridLayoutManager)
{
int orientation = ((StaggeredGridLayoutManager) layoutManager)
.getOrientation();
// StaggeredGridLayoutManager 且縱向滾動
if (orientation == StaggeredGridLayoutManager.VERTICAL)
{
childCount = childCount - childCount % spanCount;
// 如果是最後一行,則不需要繪制底部
if (pos >= childCount)
return true;
} else
// StaggeredGridLayoutManager 且橫向滾動
{
// 如果是最後一行,則不需要繪制底部
if ((pos + 1) % spanCount == 0)
{
return true;
}
}
}
return false;
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition,
RecyclerView parent)
{
int spanCount = getSpanCount(parent);
int childCount = parent.getAdapter().getItemCount();
if (isLastRaw(parent, itemPosition, spanCount, childCount))// 如果是最後一行,則不需要繪制底部
{
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
} else if (isLastColum(parent, itemPosition, spanCount, childCount))// 如果是最後一列,則不需要繪制右邊
{
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else
{
outRect.set(0, 0, mDivider.getIntrinsicWidth(),
mDivider.getIntrinsicHeight());
}
}
}
參考http://blog.csdn.net/lmj623565791/article/details/45059587
3、RecycleView.ItemAnimator
這也是個抽象類,用來處理RecycleView的Item删除和插入時的動畫,不過SDK給我們提供了一個預設的實作類DefaultItemAnimator。
4、到了RecycleView四個步驟中最重要的一步了,setAdapter。與ListView和GridView一樣,也需要設定擴充卡,不過這裡并不是繼承BaseAdapter了,而是RecycleView.Adapter。之前ListView的擴充卡中ViewHolder的使用不是強制性的,在RecycleView中已經是必須得寫的了。
public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.MyViewHolder> {
private Context context;
public MyRecyclerAdapter(List<String> list, Context context) {
this.context = context;
}
@Override
public MyRecyclerAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.content_main, null);
MyViewHolder myViewHolder = new MyViewHolder(view);
return myViewHolder;
}
@Override
public void onBindViewHolder(final MyRecyclerAdapter.MyViewHolder holder, final int position) {
holder.textView.setText("第"+position+"個Item");
}
@Override
public int getItemCount() {
return 30;
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView;
CardView cardView;
public MyViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.textView);
}
}
}
重寫這個擴充卡,主要實作三個方法、寫一個ViewHolder。
(1)onCreateViewHolder 建立ViewHolder
(2)onBindViewHolder 将ViewHolder與資料源綁定
(3)getItemCount Item的總是
實作以上四步,RecycleView的基本使用就掌握了,效果圖如下:

二、RecycleView功能進階。
1、點選事件、長按事件
RecycleView沒有提供OnClickListener和OnLongClickListener,需要我們自己實作。
在自定義的Adapter中定義一個接口,代碼如下:
private OnItemClickListener onItemClickListener;
public interface OnItemClickListener {
void OnItemClick(View view, int position);
void OnItemLongClick(View view, int position);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
在onBindViewHolder方法中調用接口中的方法
if (onItemClickListener != null) {
holder.textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getLayoutPosition();
onItemClickListener.OnItemClick((((MyViewHolder) holder).textView), pos);
}
});
holder.textView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos = holder.getLayoutPosition();
onItemClickListener.OnItemLongClick((((MyViewHolder) holder).textView), pos);
return false;
}
});
}
在MainActivity中使用時,直接調用Adapter中的方法,實作這個接口。
m_adapter.setOnItemClickListener(new MyRecyclerAdapter.OnItemClickListener() {
@Override
public void OnItemClick(View view, int position) {
Toast.makeText(MainActivity.this, "點選了" + position, Toast.LENGTH_SHORT).show();
}
@Override
public void OnItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this, "長按了" + position, Toast.LENGTH_SHORT).show();
}});
2、删除與插入
在自定義的Adapter中定義兩個方法,如下:
public void addItem(int position) {
m_listData.add(position, "Insert One");
notifyItemInserted(position);
}
public void deleteItem(int position) {
m_listData.remove(position);
notifyItemRemoved(position);
}
重新整理擴充卡的方法調用上面notifyItemInserted(position)和notifyItemRemoved(position),這樣才能執行之前設定的Item的Animation
三、RecycleView與SwipeRefreshLayout配合實作下拉重新整理、加載更多。
1、下拉重新整理
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_refresh_layout"
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.v4.widget.SwipeRefreshLayout>
//設定下拉重新整理進度條的顔色
m_swipeRefreshLayout.setColorSchemeResources(android.R.color.holo_blue_light,
android.R.color.holo_red_light, android.R.color.holo_orange_light,
android.R.color.holo_green_light);
m_swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
m_swipeRefreshLayout.postDelayed(new Runnable() {
@Override
public void run() {
m_swipeRefreshLayout.setRefreshing(false);
m_adapter.addItem(1);
}
}, 1000);
}
});
直接這樣使用,RecycleView的滾動和SwipeRefreshLayout的下拉沖突,造成每次下拉都會觸發onRefreshListener。需要判斷,隻有當RecycleView滾動到頂部時,下拉才觸發重新整理的事件。
//通過對RecycleView的滾動狀态進行監聽,當第0個位置的Item的頂部的坐标大于或等于0時,将SwipeRefreshView設定成可以重新整理的,反之設定成不可重新整理的
m_recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
//設定下拉重新整理的事件
int topRowVerticalPosition = (recyclerView == null || recyclerView.getChildCount() == 0) ? 0 : recyclerView.getChildAt(0).getTop();
m_swipeRefreshLayout.setEnabled(topRowVerticalPosition >= 0);
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
}
});
在OnScrollStateChanged中的那兩句代碼寫的很好,判斷當RecycleView的第一個Item的頂部的坐标等于0時将SwipeRefreshLayout設定成Enabled,反之不觸發滾動事件。
2、加載更多
在ListView中有addFootView方法,RecycleView沒有,是以需要我們自己判斷。
在Adapter中有getItemViewType(position)方法,可以判斷每個Item的類型。
@Override
public int getItemViewType(int position) {
if (position + 1 == getItemCount()) {
return TYPE_FOOTER;
} else {
return TYPE_ITEM;
}
}
完整的Adapter類如下:
package com.example.newviewuser;
import android.content.Context;
import android.support.v7.widget.CardView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* Created by Administrator on 2016/1/25.
*/
public class MyRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Random random = new Random();
private List<String> m_listData;
private Context context;
private List<Integer> mHeights;
private static final int GENERAL_VIEW = 0; //普通Item
private static final int FOOTER_VIEW = 1; //最底部的Item
//上拉加載更多
public static final int PULLUP_LOAD_MORE=0;
//正在加載中
public static final int LOADING_MORE=1;
//上拉加載更多狀态-預設為0
private int load_more_status=0;
private OnItemClickListener onItemClickListener;
public interface OnItemClickListener {
void OnItemClick(View view, int position);
void OnItemLongClick(View view, int position);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.onItemClickListener = onItemClickListener;
}
public MyRecyclerAdapter(List<String> list, Context context) {
m_listData = list;
this.context = context;
mHeights = new ArrayList<Integer>();
for (int i = 0; i < m_listData.size(); i++) {
mHeights.add((int) (100 + Math.random() * 300));
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == GENERAL_VIEW) {
View view = LayoutInflater.from(context).inflate(R.layout.content_main, null);
MyViewHolder myViewHolder = new MyViewHolder(view);
return myViewHolder;
} else if (viewType == FOOTER_VIEW) {
View view = LayoutInflater.from(context).inflate(R.layout.footer_layout, null);
FootViewHolder footViewHolder = new FootViewHolder(view);
return footViewHolder;
}
return null;
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
if (holder instanceof MyViewHolder) {
(((MyViewHolder) holder).textView).setText("第"+position+"個Item");
if (onItemClickListener != null) {
(((MyViewHolder) holder).textView).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getLayoutPosition();
onItemClickListener.OnItemClick((((MyViewHolder) holder).textView), pos);
}
});
(((MyViewHolder) holder).textView).setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos = holder.getLayoutPosition();
onItemClickListener.OnItemLongClick((((MyViewHolder) holder).textView), pos);
return false;
}
});
}
}else if (holder instanceof FootViewHolder){
FootViewHolder footViewHolder = (FootViewHolder) holder;
switch (load_more_status){
case PULLUP_LOAD_MORE:
footViewHolder.textView.setText("上拉加載更多...");
break;
case LOADING_MORE:
footViewHolder.textView.setText("正在加載更多資料...");
break;
}
}
}
@Override
public int getItemCount() {
return m_listData.size() + 1;
}
@Override
public int getItemViewType(int position) {
if (position + 1 == getItemCount()) {
return FOOTER_VIEW;
} else {
return GENERAL_VIEW;
}
}
public void addItem(int position) {
m_listData.add(position, "Insert One");
if (mHeights.size() < m_listData.size()) {
mHeights.add((int) (100 + Math.random() * 300));
}
notifyItemInserted(position);
}
public void deleteItem(int position) {
m_listData.remove(position);
notifyItemRemoved(position);
}
public void addMoreItem(List<String> newDatas) {
m_listData.addAll(newDatas);
notifyDataSetChanged();
}
/**
* //上拉加載更多
* PULLUP_LOAD_MORE=0;
* //正在加載中
* LOADING_MORE=1;
* //加載完成已經沒有更多資料了
* NO_MORE_DATA=2;
* @param status
*/
public void changeMoreStatus(int status){
load_more_status=status;
notifyDataSetChanged();
}
class MyViewHolder extends RecyclerView.ViewHolder {
TextView textView;
CardView cardView;
public MyViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.textView);
}
}
class FootViewHolder extends RecyclerView.ViewHolder {
TextView textView;
public FootViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.text_view_footer);
}
}
}
在RecycleView滾動的監聽中判斷,當滾動底部時,顯示加載更多的布局,改變FootView的狀态。
m_recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
//設定下拉重新整理的時間
int topRowVerticalPosition = (recyclerView == null || recyclerView.getChildCount() == 0) ? 0 : recyclerView.getChildAt(0).getTop();
m_swipeRefreshLayout.setEnabled(topRowVerticalPosition >= 0);
if (newState == RecyclerView.SCROLL_STATE_IDLE && lastVisibleItem +1 == m_adapter.getItemCount()) {
m_adapter.changeMoreStatus(MyRecyclerAdapter.LOADING_MORE);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
List<String> addString = new ArrayList<String>();
addString.add("加載更多增加的資料");
m_adapter.addMoreItem(addString);
m_adapter.changeMoreStatus(MyRecyclerAdapter.PULLUP_LOAD_MORE);
m_adapter.notifyDataSetChanged();
}
},2500);
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
lastVisibleItem = m_layoutManager.findLastVisibleItemPosition();
}
});
下載下傳源碼