本篇博客主要参考 鸿洋大神的文章进行编写,如有意见,请告知,原博客地址:(http://blog.csdn.net/lmj623565791/article/details/45059587)
充分利用时间,总结最近一直在研究的一个很实用的控件,以做如下分享:
自android 5.0后,谷歌推出了RecyclerView控件,那么我接下来从它的定义、优点、使用方法及简单拓展进行总结,意在使更多的开发替换ListView,如有错误与误导性语言和表达,请留言指出。
该控件是support-7包里的新控件,是一个强大的滑动控件,根据官方的介绍翻译:该控件用于在有限的显示窗口展示大量的数据集,所以其本质原理和ListView是相类似的,从它的名字也不难判断出,其实现原理肯定涉及复用问题,那已经有了ListView为什么还要更推崇RecyclerView呢?这就推及到第二个问题了。
通过使用RecyclerView控件,我们可以在APP中创建带有Material Design风格的复杂列表。既然官方推荐使用,那肯定存在着较ListView过人之处,现在就RecyclerView的优点进行罗列:
接下来就让我们看看,这个神奇的控件的使用:
dependencies {
...
compile 'com.android.support:recyclerview-v7:21.0.+'
}
首先我们先看下布局的实现:
"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">
.support.v7.widget.RecyclerView
android:id="@+id/my_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"/>
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
LinearLayoutManager layoutManager = new LinearLayoutManager(this );
//设置布局管理器
recyclerView.setLayoutManager(layoutManager);
//设置为垂直布局,这也是默认的
layoutManager.setOrientation(OrientationHelper. VERTICAL);
//设置Adapter
recyclerView.setAdapter( recycleAdapter);
//设置分隔线
recyclerView.addItemDecoration( new DividerGridItemDecoration(this ));
//设置增加或删除条目的动画
recyclerView.setItemAnimator( new DefaultItemAnimator());
可以从上面的使用步骤看出,相较于ListView的使用更加复杂了,这也就是RecyclerView具有高度的解偶性的表现,所谓舍得,即便代码书写增加了复杂性,但换回了可扩展性的增加。
接下来我们来继续了解RecyclerView的Adapter的使用,其实这和ListView的Adapter还是有区别的,RecyclerView的Adapter只实现三个固定方法:
接下来我们写个小的案例,直观展示:
每个条目的布局展示
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="@dimen/space_50dp"
android:gravity="center"
android:text="测试"
/>
LinearLayout>
整体代码(其中使用了注解库ButterKnife)
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.adjuz.myyupaopao.R;
import com.adjuz.myyupaopao.base.BaseActivity;
import com.cjj.MaterialRefreshLayout;
import java.util.ArrayList;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* Created by shanfuming on 2017/4/20.
*/
public class RecyclerviewAc extends BaseActivity {
private ArrayList mData = new ArrayList<>();
@BindView(R.id.recycleView)
RecyclerView mRecyclerView;
@BindView(R.id.mrl_refreshLayout)
MaterialRefreshLayout materialRefreshLayout;
private MyAdapter myAdapter;
@Override
protected void initView() {
setContentView(R.layout.activity_recyclerviewac);
ButterKnife.bind(this);
}
@Override
protected void initData() {
for (int i = 0; i < 40; i++) {
mData.add("展示-" + i);
}
myAdapter = new MyAdapter(mData);
//设置布局管理器
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
//这个是默认设置的(横纵向滑动展示)
linearLayoutManager.setOrientation(OrientationHelper.VERTICAL);
//linearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
//设置为Grid布局,需添加此布局管理器
//linearLayoutManager = new GridLayoutManager(context,columNum);
mRecyclerView.setLayoutManager(linearLayoutManager);
//设置Adapter
mRecyclerView.setAdapter(myAdapter);
}
class MyAdapter extends RecyclerView.Adapter{
private ArrayList mList;
private LayoutInflater inflater;
public MyAdapter(ArrayList list) {
this.mList = list;
inflater = LayoutInflater.from(RecyclerviewAc.this);
}
/**
* 返回我们自定义继承了ViewHolder 的MyViewHolder
* @param parent
* @param viewType
* @return
*/
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = inflater.inflate(R.layout.item_recyclerview,parent,false);
MyViewHolder myViewHolder = new MyViewHolder(view);
return myViewHolder;
}
/**
* 将view 与数据进行绑定
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.textView.setText(mList.get(position));
}
/**
* 返回数据的总个数
* @return
*/
@Override
public int getItemCount() {
return mList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder{
@BindView(R.id.tv_name)
TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this,itemView);
}
}
}
}
完成这个小案例,大家有没有觉得这个控件的好处呢,Adapter的书写,onCreateViewHolder()中初始化View,然后返回一个ViewHolder,其实就相当于ListView的Adapter中的getView()中的contentView.getTag(),包括之后的数据绑定,整体都变得更加的简洁且清晰。现在确实很不符合审美,最起码应该加上分割线,接下来就到这个模块了。
当我们去寻找类似Listview的divider属性时,会发现RecyclerView根本不支持这个属性,那我们怎么办呢?之前我们说过,可以通过RecyclerView.addItemDecoration(ItemDecoration decoration)这个方法进行设置,其中它需要的参数就是自己定义的继承自ItemDecoration的一个对象。当然像我们上面的例子ItemDecoration我们并没有设置也可以正常运行,那说明ItemDecoration并不是强制需要使用,作为开发者可以设置或者不设置Decoration。系统为我们提供了ItemDecoration,该类为抽象类,官方目前并没有提供默认的实现类,具体源码如下:
public static abstract class ItemDecoration {
public void onDraw(Canvas c,RecyclerView parent,State state) {
onDraw(c,parent);
}
public void onDrawOver(Canvas c,RecyclerView parent,State state) {
onDrawOver(c,parent);
}
public void getItemOffsets(RectoutRect, View view,RecyclerView parent,State state) {
getItemOffsets(outRect,((LayoutParams)view.getLayoutParams()).getViewLayoutPosition(),parent);
}
}
当我们调用mRecyclerView.addItemDecoration()方法添加decoration的时候,RecyclerView在绘制的时候,调用该类的onDraw和onDrawOver方法
onDraw方法先于drawChildren
onDrawOver在drawChildren之后,一般我们选择复写其中一个即可。
getItemOffsets 可以通过outRect.set()为每个Item设置一定的偏移量,主要用于绘制Decorator。
接下来我们看一下RecyclerView.ItemDecoration的实现类,该类很好的实现了RecyclerView添加分割线(当使用LayoutManager为LinearLayoutManager时)。
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;
/**
* 这个类是v-7包中自带的,并不是我自定义的
*/
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);
}
}
}
该类可设置横纵向分割线,我们在源代码的基础上加一句代码即可(此默认颜色为灰色,先不展示效果了):
mRecyclerView.addItemDecoration(new DividerItemDecoration(this,
DividerItemDecoration.VERTICAL_LIST));
该分割线是系统默认提供的,你可以在theme.xml中找到该属性的使用情况。这样方便我们去随意的改变,该属性我们可以直接声明在:
<style name="AppTheme" parent="AppBaseTheme">
<item name="android:listDivider">@drawable/divider_bg
style>
然后自己写个drawable即可,下面我们自编辑分隔符样式:
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" >
<gradient
android:centerColor="#ff00ff00"
android:endColor="#ff0000ff"
android:startColor="#ffff0000"
android:type="linear" />
<size android:height="4dp" android:width="@dimen/space_4dp"/>
shape>
当然这个样式是可以随意玩的。
不过写到这里,确实看起来和Listview 展示没区别,而且代码更烦了,但是不要着急,接下来我们尝试GridView的实现,你会发现,修改起来十分方便:
上边我们使用的代码是这样的:
//设置布局管理器
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
//这个是默认设置的
linearLayoutManager.setOrientation(OrientationHelper.VERTICAL);
mRecyclerView.setLayoutManager(linearLayoutManager);
我们之前简单介绍过它的布局管理器:当实现GridView时,我们直接替换使用GridLayoutManager(表格布局)即可,修改分分钟解决:
mRecyclerView.setLayoutManager(new GridLayoutManager(this,4));
//StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL);
这样对于前面的分割线就不再适用了,因为DividerItemDecoration 的计算绘制,是针对一行多个item进行的,但是GridView 是多行了,所以就要进行多次绘制,并且GridLayoutManager时,Item如果为最后一列(则右边无间隔线)或者为最后一行(底部无分割线)。
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);
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);
}
接下来我们直接引用 鸿洋大神 定义的分割线了:
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.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;
import android.support.v7.widget.RecyclerView.State;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.view.View;
/**
*
* @author zhy
*
*/
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, State state)
{
drawHorizontal(c, parent);
drawVertical(c, parent);
}
private int getSpanCount(RecyclerView parent)
{
// 列数
int spanCount = -1;
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)
{
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)
{
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());
}
}
}
不要忘记修改代码:
DividerGridItemDecoration dividerGridItemDecoration = new DividerGridItemDecoration(this);
mRecyclerView.addItemDecoration(dividerGridItemDecoration);
怎么样,到这里,是不是觉得RecyclerView真的很强大了,样式的切换只需要几行代码的改变,如此简便!还有横向的滑动效果(这里就不展示效果了,可以写下试试):
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.HORIZONTAL);
//设置布局管理器
mRecyclerView.setLayoutManager(layoutManager);
值得注意的是,当StaggeredGridLayoutManager布局管理器的第二个参数是StaggeredGridLayoutManager.VERTICAL时,第一个传入的int参数代表是行数;当StaggeredGridLayoutManager布局管理器的第二个参数是StaggeredGridLayoutManager.HORIZONTAL时,第一个传入的int参数代表是列数,这个效果和上边的是一样的;
可以看到,固定行数或列数,随意改变横向纵向,要注意的是,横向滑动时,要改变下item 布局的宽度,具体什么原因,大家写写就知道了。
在Activity 中添加一下代码:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId())
{
case R.id.action_add:
myAdapter.addData(1);
break;
case R.id.action_remove:
myAdapter.removeData(1);
break;
}
return true;
}
"1.0" encoding="utf-8"?>
R.menu.main(在res 文件下创建menu文件夹下添加main.xml)代码 :
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="example.gmariotti.it.test21.TaylorActivity">
<item android:id="@+id/action_add"
android:title="ADD"
android:orderInCategory="100"
app:showAsAction="always" />
<item android:id="@+id/action_remove"
android:title="REMOVE"
android:orderInCategory="100"
app:showAsAction="always" />
menu>
在Adapter 中添加以下两个函数:
/**
* 动态增加或删除条目
* @param position
*/
public void addData(int position) {
mList.add(position, "add One");
notifyItemInserted(position);
notifyDataSetChanged();
}
public void removeData(int position) {
mList.remove(position);
notifyItemRemoved(position);
notifyDataSetChanged();
}
这里我们更新数据使用的不是notifyDataSetChanged() 而是notifyItemInsert()和notifyItemRemove() 否则没有动画效果(但这里还是加了notifyDataSetChanged,是因为动态添加多个item时,点击事件位置不对,调用了就不会有问题);
系统为我们提供了默认实现类,借助默认的实现,当Item添加和移除的时候,添加动画效果很简单:
//设置增加或删除条目的动画
mRecyclerView.setItemAnimator( new DefaultItemAnimator());
相信大家在使用ListView时已经习惯了设置点击事件,它给我们提供了封装好的OnItemClickListener,然而对于RecyclerView,很可惜并没有给我们提供任何的点击监听,但我们可以模仿自己添加监听方法,接下来我们在Adapter 中添加两个函数:
class HomeAdapter extends RecyclerView.Adapter
{
//...
public interface OnItemClickLitener
{
void onItemClick(int position);
void onItemLongClick(int position);
}
private OnItemClickLitener mOnItemClickLitener;
public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener)
{
this.mOnItemClickLitener = mOnItemClickLitener;
}
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position)
{
holder.textview.setText(mList.get(position));
// 如果设置了回调,则设置点击事件
if (mOnItemClickLitener != null)
{
holder.itemView.setOnClickListener(new OnClickListener()
{
@Override
public void onClick(View v)
{
mOnItemClickLitener.onItemClick(position);
}
});
holder.itemView.setOnLongClickListener(new OnLongClickListener()
{
@Override
public boolean onLongClick(View v)
{
mOnItemClickLitener.onItemLongClick(position);
return false;
}
});
}
}
//...
}
正如以上所写,添加了自定义点击事件的监听接口,然后在onBindViewHolder 进行相应设置。
在activity 中代码实现:
myAdapter.setOnItemClickListener(new MyAdapter.OnItemClickListener() {
@Override
public void onClick1(int position) {
Toast.makeText(RecyclerviewAc.this,"点击了第"+position+"位置",Toast.LENGTH_SHORT).show();
}
@Override
public void onLongClick(int position) {
Toast.makeText(RecyclerviewAc.this,"长按了第"+position+"位置",Toast.LENGTH_SHORT).show();
}
});
这里就不上效果图了,点击效果很简单,一个提示而已。到这里RecyclerView的点击事件也基本结束。
给大家推荐一个不错的刷新和加载控件,操作简单,配合RecyclerView一起使用,可满足大多数需求了。
这次总结就到这里,如果有什么建议或意见,欢迎留言!再次感谢两篇博文的参考。
源码地址
以上博文参考地址:(http://blog.csdn.net/skykingf/article/details/50827141)
[http://blog.csdn.net/lmj623565791/article/details/45059587]