1.RecycleView是什么?
自Android 5.0之后,谷歌公司推出了RecyclerView控件,RecyclerView是support-v7包中的新组件,这是一个比ListView更为强大的滑动组件,与经典的ListView相比,同样拥有item回收复用的功能,这一点从它的名字recyclerview即回收view也可以看出,RecyclerView只关心回收与复用View,其它的可以自己设置。从这点看出其高度解耦的特点,给予你充分的定制自由(所以你才可以轻松的通过这个控件实现ListView,GirdView,瀑布流等效果)。相对于经典的ListView控件RecyclerView自然拥有ListView所不具备的优点。
2.RecyclerView的优点
根据谷歌官方的介绍RecylerView是ListView的升级版,不仅可以轻松实现和ListView同样的效果,还优化了ListView中存在的各种不足之处。目前,Android官方更加推荐使用RecyclerView,既然如此RecyclerView在保留了ListView的种种优点之外肯定还会有其特有的一些优点。接下来就比较RecyclerView和ListView:
①RecyclerView封装了viewholder的回收复用,也就是说RecyclerView标准化了ViewHolder,编写Adapter面向的是 ViewHolder而不再是View了,复用的 逻辑被封装了,写起来更加简单。
②提供了一种插拔式的体验,高度的解耦,异常的灵活,针对一个Item的显示RecyclerView专门抽取出了相应的类, 来控制Item的显示,使其的扩展性非常强。例如:你想控制横向或者纵向滑动列表效果可以通过 LinearLayoutManager这个类来进行控制(与GridView效果对应的是GridLayoutManager,与瀑布流对应的还有 StaggeredGridLayoutManager等),也就是说RecyclerView不再拘泥于ListView的线性展示方式,它也可以实现 GridView的效果等多种效果。你想控制Item的分隔线,可以通过继承RecyclerView的ItemDecoration这个类,然后 针对自己的业务需求去抒写代码。
③可以控制Item增删的动画,可以通过ItemAnimator这个类进行控制,当然针对增删的动画,RecyclerView有其自己 默认的实现。
3.RecyclerView的用法
(1)线性布局
在使用RecyclerView之前必须要导入support-v7包,在Android Studio中需要在build.gradle文件中添加相应的依赖库才行,因此需要在build.gradle文件中的dependencies{}闭包中添加compile 'com.android.support:recyclerview-v7:23.2.1',添加完成以后记得要点击一下Sync Now来进行同步,如下面的代码所示。
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:23.4.0'
testCompile 'junit:junit:4.12'
compile 'com.android.support:recyclerview-v7:23.2.1'
}
在布局文件中,入下所示:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="5dp"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.administrator.recyclerview.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
在布局中加入RecyclerView也是非常简单的,先给RecyclerView指定一个id,然后将宽度和高度都设置为match_parent,这样RecyclerView就占满整个布局的空间,这里需要注意的是,由于RecyclerView并不是内置在系统的SDK当中的,所以需要把完整的路径写出来。
在开始使用RecyclerView之前跟使用ListView差不多,都需要自定义一个适配器,与ListView的适配器继承自BaseAdapter不同的是,RecyclerView的适配器需要继承自RecyclerView.Adapter,并将泛型指定为RecyclerViewAdapter.RecyclerViewHolder,其中,RecyclerViewHolder是我们在RecyclerViewAdapter中定义的一个内部类,除此之外还需要实现onCreateViewHolder(),onBindViewHolder()和getItemCount()这三个方法。
package com.example.administrator.recyclerview;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* Created by ChuPeng on 2017/3/31.
*/
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder>
{
private List<User> userList;
private ItemClickListener itemClickListener;
public RecyclerViewAdapter(List<User> userList)
{
super();
this.userList = userList;
}
public int getItemCount()
{
return userList.size();
}
//初始化一个ViewHolder
public RecyclerViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType)
{
View view = View.inflate(viewGroup.getContext(), R.layout.recyclerview_item, null);
//创建一个ViewHolder
RecyclerViewHolder viewHolder = new RecyclerViewHolder(view)
{
public String toString()
{
return super.toString();
}
};
return viewHolder;
}
public void onBindViewHolder(RecyclerViewHolder holder, int position)
{
User user = userList.get(position);
holder.userNmae.setText(user.getUserName());
holder.imageName.setBackgroundResource(user.getImageView());
}
//自定义一个ViewHolder
public class RecyclerViewHolder extends RecyclerView.ViewHolder
{
private TextView userNmae;
private ImageView imageName;
public RecyclerViewHolder(View itemView)
{
super(itemView);
this.imageName = (ImageView) itemView.findViewById(R.id.image);
this.userNmae = (TextView) itemView.findViewById(R.id.name);
}
}
}
①onCreateViewHolder()这个方法主要生成为每个Item inflater出一个View,但是该方法返回的是一个ViewHolder。该方法把View直接封装在ViewHolder中,然后我们面向的是ViewHolder这个实例,当然这个ViewHolder需要我们自己去编写。直接省去了当初的convertView.setTag(holder)和convertView.getTag()这些繁琐的步骤。
②onBindViewHolder()
这个方法主要用于适配渲染数据到View中。方法提供给你了一个ViewHolder,而不是原来的convertView。
这个方法主要用于对RecyclerView的子项数据进行赋值,会在每个子项被滚动到屏幕内的时候执行,在这个方法中会提供两个参数,一个参数是ViewHolder,这里的ViewHolder来自于onCreateViewHolder()的返回值,另一个参数是position,这里我们通过position参数得到当前项User的实例,然后再将数据设置到ViewHolder的ImageView和TextView当中即可。
③getItemCount()这个方法就类似于BaseAdapter的getCount方法了,它用于告诉RecyclerView一共有多少子项,直接返回数据源的长度就可以了。
可以看到RecyclerView标准化了ViewHolder,编写 Adapter面向的是ViewHoder而不在是View了,复用的逻辑被封装了,写起来更加简单。其实它的写法与BaseAdapter的写法是差不多的,大家可以对比下它与getView方法写法的区别,在onCreateViewHolder方法中初始化了一个View,然后返回一个ViewHolder,这个返回的ViewHolder类似于之前在getView中的convertView.getTag(),然后在onBindViewHolder方法中去给这个ViewHolder中的控件填充值。其实它的原理跟getView是差不多的,只是做了封装,我们写起来比较简洁。
在创建完RecyclerView的Adapter以后,还需要将Adapter设置到RecyclerView中去。
package com.example.administrator.recyclerview;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.widget.LinearLayout;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity
{
private RecyclerView recyclerView;
private List<User> userList;
private RecyclerViewAdapter adapter;
private LinearLayoutManager linearLayoutManager;
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
userList = new ArrayList<User>();
for(int i = 0; i <= 20; i++)
{
User user = new User();
user.setImageView(R.mipmap.ic_launcher);
user.setUserName("我是Item" + i);
userList.add(user);
}
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
//创建一个线性布局管理器
linearLayoutManager = new LinearLayoutManager(this);
//设置为垂直布局,这是默认的
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
//设置布局管理器
recyclerView.setLayoutManager(linearLayoutManager);
//新建一个适配器实例
adapter = new RecyclerViewAdapter(userList);
//设置Adapter
recyclerView.setAdapter(adapter);
}
}
通过上面的代码可以看到对RecylerView设置Adapter的过程,上面的的过程比ListView要复杂一些,这也是因为RecyclerView高度解耦的表现,虽然在代码的书写上增加复杂度,但是它拥有极高的扩展性。
现在运行一下程序可以看到:
通过上面的例子可以看出,我们使用RecyclerView实现了和ListView几乎一模一样的效果,虽然说在代码量方面并没有减少,但是逻辑变得更加清晰了,但是目前的RecyclerView还没有item之间的分割线,接下来我们还需要通过RecyclerView.addItemDecoration()给RecyclerView中添加item之间的分割线。
RecyclerView.addItemDecoration()需要传入一个参数,该参数为RecyclerView.ItemDecoration,它是一个抽象类。也就是说我们要加入分隔线必须自定义一个类并且实现这个抽象类。RecyclerView.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(Rect outRect, View view, RecyclerView parent, State state)
{
getItemOffsets(outRect, ((LayoutParams) view.getLayoutParams()).getViewLayoutPosition(),parent);
}
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent)
{
outRect.set(0, 0, 0, 0);
}
}
从上面的抽象类中可以看出有onDraw()、onDrawOver()和getItemOffsets()这三种方法,RecyclerView在绘制的过程中,会先调用该抽象类中的onDraw()和onDrawOver()方法绘制Decorator,onDraw()方法优先于drawChildren,而onDrawOver()方法在drawChildren之后调用,因此一般情况下这两个方法我们只用重写一个即可,getItemOffsets()方法可以通过outRect.set()为每个Item设置一定的偏移量,主要用于绘制Decorator。
接下来看一个RecyclerView.ItemDecoration的实现类,当使用的LayoutManager为LinearLayoutManager时,该类可以很好的为RecyclerView添加分隔线。package com.example.administrator.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.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;
}
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)
{
//得到RecyclerView距界面左边的距离,其实就是item左上角的横坐标
final int left = parent.getPaddingLeft();
//得到RecyclerView距界面右边的距离,其实就是item右下角的横坐标
final int right = parent.getWidth() - parent.getPaddingRight();
//得到Item的数量
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++)
{
//得到Item的对象
final View child = parent.getChildAt(i);
//得到Item参数
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
//计算item左上角的纵坐标
final int top = child.getBottom() + params.bottomMargin;
//计算item右下角的纵坐标
final int bottom = top + mDivider.getIntrinsicHeight();
//根据Item的左上角和右下角坐标绘制矩形
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);
}
}
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);
}
}
}
从上面的代码中可以看出,DividerItemDecoration直接使用了系统主题中的android.R.attr.listDivider做为Item之间的分隔线,获取到listDivider之后在getItemOffsets中,outRect去设置了绘制的范围,onDraw中实现了真正的绘制。在写好DividerItemDecoration之后只需要在代码中添加
//设置Item的分割线
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
就可以给Item设置分隔线效果,设置好的效果如下所示:
如上面所示,所使用的分割线效果是系统自带的属性,如果想要自定义Item分隔线效果可以在theme.xml中找到该属性并且进行修改。
除了使用上面的方法绘制出item分隔线之外,还可以通过设置Item距离父控件的边距来实现Item分隔线效果,使用这种方式比较简单,也更容易理解。只需要在Item的布局文件中加入android:layout_margin属性即可,入下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
android:layout_margin="5dp"
android:orientation="vertical">
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center"
android:src="@mipmap/ic_launcher"/>
<TextView
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="5"
android:textSize="25sp"
android:textColor="#000000"
android:layout_gravity="center"
android:text="我是Item1"/>
</LinearLayout>
顺便通过设置的方向,将RecyclerView变为水平滑动
//设置滚动方向为水平方向,如果不设置默认为竖直方向
layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
通过以上的设置,效果如下:
上面的设置实现了类似于ListView样子的Demo,但是还是缺少了点击效果,令人郁闷的是系统也没有为我们提供可以直接使用的ClickListener和LongClickListener,这就需要我们自己去添加点击效果,在这里我们选择通过在adapter中添加点击的回调方法。
首先需要在接口中定义几种点击类型
package com.example.administrator.recyclerview;
import android.view.View;
/**
* Created by ChuPeng on 2017/3/31.
*/
public interface ItemClickListener
{
//Item 普通点击
public void onItemClick(View view, int position);
//Item 长按
public void onItemLongClick(View view, int position);
//Item 内部View点击
public void onItemSubViewClick(View view, int position);
}
然后需要在adap
ter中添加点击的回调
package com.example.administrator.recyclerview;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* Created by ChuPeng on 2017/3/31.
*/
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.RecyclerViewHolder>
{
private List<User> userList;
private String orientation;
private ItemClickListener itemClickListener;
public RecyclerViewAdapter(List<User> userList, String orientation)
{
super();
this.userList = userList;
this.orientation = orientation;
}
//设置回调
public void setItemClickListener(ItemClickListener itemClickListener)
{
this.itemClickListener = itemClickListener;
}
public int getItemCount()
{
return userList.size();
}
public RecyclerViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType)
{
View view;
if("vertical".equals(orientation))
{
view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recyclerview_item_vertical, null);
}
else
{
view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.recyclerview_item_horizontal, null);
}
//创建一个ViewHolder
RecyclerViewHolder viewHolder = new RecyclerViewHolder(view);
return viewHolder;
}
public void onBindViewHolder(final RecyclerViewHolder holder, final int position)
{
User user = userList.get(position);
holder.userNmae.setText(user.getUserName());
holder.imageName.setBackgroundResource(user.getImageView());
//为image添加监听回调
holder.imageName.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
//如果设置了回调,则设置点击事件
if(itemClickListener != null)
{
itemClickListener.onItemSubViewClick(holder.imageName, position);
}
}
});
}
public class RecyclerViewHolder extends RecyclerView.ViewHolder
{
private TextView userNmae;
private ImageView imageName;
public RecyclerViewHolder(final View itemView)
{
super(itemView);
this.imageName = (ImageView) itemView.findViewById(R.id.image);
this.userNmae = (TextView) itemView.findViewById(R.id.name);
//为item添加普通点击回调
itemView.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
//如果设置了回调,则设置点击事件
if(itemClickListener != null)
{
itemClickListener.onItemClick(itemView, getPosition());
}
}
});
//为item添加长按回调
itemView.setOnLongClickListener(new View.OnLongClickListener()
{
public boolean onLongClick(View v)
{
//如果设置了回调,则设置点击事件
if(itemClickListener != null)
{
itemClickListener.onItemLongClick(itemView, getPosition());
}
return true;
}
});
}
}
}
通过上面的代码可以看出,相比较普通的adapter来说,加入点击事件就是在adapter的onBindViewHolder()方法或者RecyclerViewHolder()方法中加入某一项或者是某个View点击事件的自定义回调接口。最后还需要在Activity中设置adapter的点击监听的实现。
adapter.setItemClickListener(new ItemClickListener()
{
public void onItemClick(View view, int position)
{
Toast.makeText(linearLayoutVerticalActivity.this, "你点击了Item", Toast.LENGTH_SHORT).show();
}
public void onItemLongClick(View view, int position)
{
Toast.makeText(linearLayoutVerticalActivity.this, "你长按了Item", Toast.LENGTH_SHORT).show();
}
public void onItemSubViewClick(View view, int position)
{
Toast.makeText(linearLayoutVerticalActivity.this, "你点击了Image", Toast.LENGTH_SHORT).show();
}
});
通过设置以后的效果如下:
通常在ListView中实现Item的增加或删除操作,都是对数据源进行操作,并且通过notifyDataSetChanged()方法更新数据达到ListView刷新的效果。在RecyclerView中想要对Item实现增加或删除操作并且有动画效果也是对数据源进行操作,不同的是需要通过notifyItemInserted(position)或notifyItemRemoved(position)刷新数据,否则将不会有动画效果。 在Activity中做出如下修改。
//设置Item增加、移出动画
recyclerView.setItemAnimator(new DefaultItemAnimator());
addButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
User user = new User();
user.setUserName("Insert One");
user.setImageView(R.mipmap.ic_launcher);
if(userList.size() > 2)
{
userList.add(2, user);
adapter.notifyItemInserted(2);
}
else
{
userList.add(0, user);
adapter.notifyItemInserted(0);
}
}
});
removeButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
if(userList.size() > 3)
{
userList.remove(2);
adapter.notifyItemRemoved(2);
}
else
{
if(userList.size() > 0)
{
userList.remove(0);
adapter.notifyItemRemoved(0);
}
else
{
Toast.makeText(linearLayoutVerticalActivity.this, "列表中无数据", Toast.LENGTH_SHORT).show();
}
}
}
});
在上面的代码中增加/删除都是从列表中的第三个Item开始,直到列表中不足三个Item时增加/删除的操作就变为从第一个Item开始,通过添加增加/删除后的效果入下所示:
可以看到通过这样的方法实现了在不同方向上带有动画效果的增加/删除操作。
(2)网格布局
至此,RecyclerView的用法就已经介绍的差不多了,但是你肯定会发现像上面的那种使用RecyclerView去模仿ListView真的好麻烦,还不如直接用ListView来的简单快速,那么请继续往下看。在Activity的代码中,可以看到这样的代码
//创建一个线性布局管理器
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
//设置滚动方向为竖直方向,如果不设置默认为竖直方向
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
//设置布局管理器
recyclerView.setLayoutManager(layoutManager);
在上面的代码中是通过系统默认的LinearLayoutManager,设置好滚动的方向以后再将这个布局管理器设置给RecyclerView即可。RecyclerView.LayoutManager是一个抽象类,系统提供了三个实现类,LinearLayoutManager就是其中一个实现类,使用这三个实现类可以快速的通过RecyclerView分别实现线性布局、网格布局、瀑布式布局。
- LinearLayoutManager 线性布局管理器,支持横向、纵向
- GridLayoutManager 网格布局管理器,支持横向、纵向
- StaggeredGridLayoutManager 瀑布布局管理器,支持横向、纵向
GridLayoutManager gridLayoutManager = new GridLayoutManager(gridLayoutVerticalActivity.this, 3, GridLayoutManager.VERTICAL, false);
recyclerView.setLayoutManager(gridLayoutManager);
只需要修改布局管理器为GridLayoutManager即可,从GridLayoutManager的构造函数中可以看出总共接收4个参数,第一个参数是context,第二个参数是按布局方向上有几个Item,第三个参数是布局方向,第四个参数设置是否反转。把布局管理器改为GridLayoutManager后前面使用的DividerItemDecoration绘制分隔线在这里明显已经不适用了,因为在LinearLayoutManager中每一行只有一个Item,而在GridLayoutManager中每一行有多个Item。
package com.example.administrator.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.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;
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());
}
}
}
在上面的代码中是在每行每列都绘制分隔线,在getItemOffsets()方法中去判断当前将要绘制的是否是最后一行,如果是则不需要绘制底部,判断是否是最后一列,如果是则不需要绘制右边。一般情况下如果仅仅希望通过Item之间的空隙来代替分隔线,那么设置Item布局的android:layout_margin属性倒是一种更好的方式,最后的效果为:
addButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
User user = new User();
user.setUserName("Insert One");
user.setImageView(R.mipmap.ic_launcher);
if(userList.size() > 2)
{
userList.add(2, user);
adapter.notifyItemInserted(2);
}
else
{
userList.add(0, user);
adapter.notifyItemInserted(0);
}
}
});
removeButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
if(userList.size() > 3)
{
userList.remove(2);
adapter.notifyItemRemoved(2);
}
else
{
if(userList.size() > 0)
{
userList.remove(0);
adapter.notifyItemRemoved(0);
}
else
{
Toast.makeText(gridLayoutVerticalActivity.this, "列表中无数据", Toast.LENGTH_SHORT).show();
}
}
}
});
实现后的效果为:
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(staggeredGridLayoutManager);
这里的StaggeredGridLayoutManager构造函数总共接收两个参数,第一个参数表示有多少列或者多少行,第二个参数表示瀑布式布局滑动的方向,如果传入的是StaggeredGridLayoutManager.VERTICAL代表有多少列;那么传入的如果是StaggeredGridLayoutManager.HORIZONTAL就代表有多少行,实现后的效果为:
addButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
User user = new User();
user.setUserName("Insert One");
user.setImageView(R.mipmap.ic_launcher);
if(userList.size() > 2)
{
userList.add(2, user);
adapter.notifyItemInserted(2);
}
else
{
userList.add(0, user);
adapter.notifyItemInserted(0);
}
}
});
removeButton.setOnClickListener(new View.OnClickListener()
{
public void onClick(View v)
{
if(userList.size() > 3)
{
userList.remove(2);
adapter.notifyItemRemoved(2);
}
else
{
if(userList.size() > 0)
{
userList.remove(0);
adapter.notifyItemRemoved(0);
}
else
{
Toast.makeText(gridLayoutVerticalActivity.this, "列表中无数据", Toast.LENGTH_SHORT).show();
}
}
}
});
实现后的效果为: