在上一篇 RecyclerView 控件的文章中,我们看了一下ListView控件和RecyclerView控件的简单用法,那么下面我们将关注点放在RecyclerView上,毕竟RecyclerView控件在很多方面确实比ListView控件更好用。下面来看一下怎么对RecyclerView中的子项添加分隔线:
首先,我们要知道,要对RecyclerView控件中的子项添加分隔线,我们要利用RecyclerView.ItemDecoration类来实现。我们要继承RecyclerView.ItemDecoration类并且重写里面的方法来实现,一般来说,一个自定义的ItemDecoration类的基本写法:
public class ItemDecoration extends RecyclerView.ItemDecoration {
// 在这个方法中绘制分隔线,这个方法会在RecyclerView中的子项绘制完成之前被调用
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
}
// 也可以在这个方法中绘制分隔线,这个方法会在RecyclerView中的子项绘制完成之后被调用
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
// 这个方法用于对子项的绘制位置进行一些必要的调整或者对子项进行一些其他的调整。第一个参数为子项的绘制位置,第二个参数为正在绘制的子项View的引用
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
}
}
其实,onDraw 和 onDrawOver 方法我们只需要重写其中一个就行了,因为两个方法唯一的不同就是调用的先后问题。下面用一个小例子来看一下RecyclerView.ItemDecoration类的具体用法,新建一个Android工程:
首先,如果我们要使用RecyclerView控件,我们必须对它添加构建依赖:
在Android studio 工程中的app目录下的buil.gradle文件中加上上图划出的代码,之后点击右上角的 async now 蓝色字体,android studio 就会为我们添加对RecyclerView控件的依赖,我们就可以在程序中使用它,下面是 activity_main.xml:
<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:gravity="center_horizontal"
tools:context="com.example.administrator.blogrecyclerview.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 控件准备显示的数据,通过RecyclerView.Adapter<RecyclerView.ViewHolder> 这个RecyclerView 自带的适配器类来实现如果对这个类的使用还不是很熟悉的,可以看一下http://blog.csdn.net/hacker_zhidian/article/details/56292052这篇文章,ok,我们继续,新建一个布局文件item_layout.xml作为RecyclerView控件的子项视图:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView"
android:textSize="40sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
LinearLayout>
为了简单起见,我们只是使用一个TextView控件,这样的话RecyclerView的子项就只能显示文字,当然我们可以根据需求定制布局文件。接下来是自定义的适配器类MyRecyclerViewAdapter.java:
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
/**
* Created by Administrator on 2017/2/26.
*/
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyViewHolder> {
private Context myContext = null;
private ArrayList textViewList = null;
public MyRecyclerViewAdapter(Context context, ArrayList list) {
myContext = context;
textViewList = list;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).
inflate(R.layout.item_layout, parent, false);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(myContext, ((TextView) v.findViewById(R.id.textView)).getText(),
Toast.LENGTH_SHORT).show();
}
});
MyViewHolder myViewHolder = new MyViewHolder(view);
return myViewHolder;
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.textView.setText(textViewList.get(position));
}
@Override
public int getItemCount() {
return textViewList.size();
}
}
class MyViewHolder extends RecyclerView.ViewHolder{
public TextView textView;
public MyViewHolder(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.textView);
}
};
这里就不多介绍了,在前文的链接中有对于RecyclerView.Adapter类的介绍,那么最后就是MainActivity.java了:
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.widget.LinearLayout;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
private ArrayList stringList = new ArrayList();
private RecyclerView recyclerView = null;
private MyRecyclerViewAdapter myRecyclerViewAdapter = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
initAdapter();
// 新建一个线性布局管理器,并且设置排布方向为竖直方向
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(linearLayoutManager); // 设置子项排布方向
recyclerView.setAdapter(myRecyclerViewAdapter); // 设置适配器
}
public void initAdapter() { // 初始化显示的数据和适配器
String str = null;
for(int i = 0; i < 20; i++) {
str = "项目" + (i+1);
stringList.add(str);
}
myRecyclerViewAdapter = new MyRecyclerViewAdapter(this, stringList);
}
}
好了,我们先来看一下效果:
可以看到,我们这里的显示子项之中并没有分割线,那么怎么添加分割线呢?我们对文章开始的继承于 RecyclerView.ItemDecoration 类的自定义类来进行改写,加入我们自己的逻辑:
在工程中新建一个类MyItemDecoration.java:
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
/**
* Created by Administrator on 2017/2/26.
*/
public class MyItemDecoration extends RecyclerView.ItemDecoration {
/*
* 定义4个常量值,代表布局方向,分别是竖向线性布局、横向线性布局、竖向网格布局、横向网格布局
*/
public static final int LINEAR_LAYOUT_ORIENTATION_VERTICAL = 0;
public static final int LINEAR_LAYOUT_ORIENTATION_HORIZONTAL = 1;
public static final int GRID_LAYOUT_ORIENTATION_VERTICAL = 2;
public static final int GRID_LAYOUT_ORIENTATION_HORIZONTAL = 3;
private int orientation = -1; // 当前的布局方向
// 如果是网格布局我们要计算出每一行或者每一列(取决于布局方向)中的子项数目
private int rawOrColumnSum = 0;
// Drawable 对象用于绘制分隔线
private Drawable myDivider = null;
public MyItemDecoration(Context context, int orientation) {
/* 这个构造方法用于处理线性布局传入的情况,我们要对myDivider对象进行初始化
* (绘制的颜色和宽度等等)
* R.drawable.my_list_divider 是我们自定义的一个drawable资源文件,我们通过
* myContext来获取它
*/
myDivider = context.getResources().getDrawable(R.drawable.my_list_divider);
if(orientation == LinearLayoutManager.HORIZONTAL) {
this.orientation = LINEAR_LAYOUT_ORIENTATION_HORIZONTAL;
}else if(orientation == LinearLayoutManager.VERTICAL) {
this.orientation = LINEAR_LAYOUT_ORIENTATION_VERTICAL;
}
}
public MyItemDecoration(Context context, int orientation, int rawOrColumnSum) {
// 这个构造方法用于处理网格布局传入的情况,原理同上
myDivider = context.getResources().getDrawable(R.drawable.my_list_divider);
if(orientation == GridLayoutManager.HORIZONTAL) {
this.orientation = GRID_LAYOUT_ORIENTATION_HORIZONTAL;
} else if(orientation == GridLayoutManager.VERTICAL) {
this.orientation = GRID_LAYOUT_ORIENTATION_VERTICAL;
}
this.rawOrColumnSum = rawOrColumnSum;
}
// 在这个方法中。我们对布局方向进行判断,由此来调用正确的分隔线绘制方法
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
if(orientation == LINEAR_LAYOUT_ORIENTATION_HORIZONTAL ||
orientation == LINEAR_LAYOUT_ORIENTATION_VERTICAL) {
linearLayoutDrawItemDecoration(c, parent);
} else if(orientation == GRID_LAYOUT_ORIENTATION_HORIZONTAL ||
orientation == GRID_LAYOUT_ORIENTATION_VERTICAL) {
gridLayoutItemDecoration(c, parent);
}
}
/*
* 当排布方式为线性布局的时候,绘制分割线的方法:
*/
private void linearLayoutDrawItemDecoration(Canvas canvas, RecyclerView parent) {
int childCount = parent.getChildCount(); // 获取RecyclerView控件中的子控件总数
int left, top, right, bottom;
View child = parent.getChildAt(0);
// 获取分割线的高度(把分割线看成一个小矩形)
int drawableHeight = myDivider.getIntrinsicHeight();
// 如果是竖直排布,那么分割线为横线
if(orientation == LINEAR_LAYOUT_ORIENTATION_VERTICAL) {
left = parent.getLeft();
right = parent.getRight(); // 获取子控件开始 x 坐标和结束 x 坐标
for (int i = 1; i < childCount; i++) {
top = child.getBottom() - drawableHeight/2; // 获取开始点y坐标
bottom = child.getBottom() + drawableHeight/2; // 获取结束点y坐标
myDivider.setBounds(left, top, right, bottom); // 设置绘制区域,下同
myDivider.draw(canvas); // 在Canvas对象上绘制区域
child = parent.getChildAt(i);
}
// 如果是水平排布,那么分割线为竖线
} else if(orientation == LINEAR_LAYOUT_ORIENTATION_HORIZONTAL) {
top = child.getTop();
bottom = child.getBottom(); // 获取子控件的开始 y 坐标和结束 y 坐标
for(int i = 1; i < childCount; i++) {
left = child.getRight() - drawableHeight/2; // 获取开始点 x 坐标
right = child.getRight() + drawableHeight/2; // 获取结束点 x 坐标
myDivider.setBounds(left, top, right, bottom); // 设置绘制区域
myDivider.draw(canvas);
child = parent.getChildAt(i);
}
}
}
/*
* 当排布方式为网格布局的时候,分割线的绘制方法:
*/
private void gridLayoutItemDecoration(Canvas canvas, RecyclerView parent) {
// 顺着布局方向上的要绘制的分割线条数
int childCount = parent.getChildCount();
int lineSum = childCount / rawOrColumnSum - 1;
lineSum += childCount % rawOrColumnSum == 0 ? 0 : 1;
// 获取分割线的高度(把分割线看成一个小矩形)
int drawableHeight = myDivider.getIntrinsicHeight();
int left, right, top, bottom;
View child = parent.getChildAt(0);
// 布局方向为竖直排布方式
if(orientation == GRID_LAYOUT_ORIENTATION_VERTICAL) {
left = parent.getLeft();
right = parent.getRight();
for(int i = 0; i < lineSum; i++) { // 循环用于绘制横向分割线
child = parent.getChildAt(i*rawOrColumnSum);
top = child.getBottom() - drawableHeight/2;
bottom = child.getBottom() + drawableHeight/2;
myDivider.setBounds(left, top, right, bottom);
myDivider.draw(canvas);
}
top = parent.getTop();
bottom = parent.getBottom();
for(int i = 0; i < rawOrColumnSum-1; i++) { // 循环用于绘制竖向分割线
child = parent.getChildAt(i);
left = child.getRight() - drawableHeight/2;
right = child.getRight() + drawableHeight/2;
myDivider.setBounds(left, top, right, bottom);
myDivider.draw(canvas);
}
// 布局方向为横向排布方式
} else if(orientation == GRID_LAYOUT_ORIENTATION_HORIZONTAL) {
top = parent.getTop();
bottom = parent.getBottom();
for(int i = 0; i <= lineSum; i++) { // 循环绘制竖向分割线
child = parent.getChildAt(i*rawOrColumnSum);
left = child.getRight() - drawableHeight/2;
right = child.getRight() + drawableHeight/2;
myDivider.setBounds(left, top, right, bottom);
myDivider.draw(canvas);
}
left = parent.getLeft();
right = parent.getRight();
for(int i = 0; i < rawOrColumnSum; i++) { // 循环绘制横向分割线
child = parent.getChildAt(i);
top = child.getBottom() - drawableHeight/2;
bottom = child.getBottom() + drawableHeight/2;
myDivider.setBounds(left, top, right, bottom);
myDivider.draw(canvas);
}
}
}
}
代码看似有点多,但是主要的逻辑不复杂:对RecyclerView的子项排布方式进行判断,两个方法分别用于绘制线性布局的分隔线和网格布局的分隔线,这两个方法主要是通过布局和控件的位置来计算出myDivider绘制的区域的一些位置信息,如果对于布局和控件的熟悉的话就没什么难点了。我们注意到这里的分隔线使用了我们自定义的资源,因此,我们要在drawable文件夹中新建一个资源文件my_list_divider.xm:
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<size android:height="4dp">size>
<gradient
android:startColor="#ff0000"
android:centerColor="#00ff00"
android:endColor="#0000ff">
gradient>
shape>
我们在这个资源文件中设置了myDivider对象的线宽(4dp)和颜色(红、绿、蓝的渐变颜色效果),OK, 一个RecyclerView控件的分隔线绘制就完成了,我么还需要对MainActivity.java进行小小的修改才能显示出分割线的效果:
就是为我们RecyclerView控件添加一个MyItemDecoration对象来实现分割线的绘制,好了,让我们运行一下:
这里为了显示出滑动的效果,故意多加了点数据,这里是RecyclerView控件中的子项排布方式为竖值的分隔线效果,那么水平呢?让我们来改一下MainActivity.java:
改了一下线性布局的排布方式,为了美观,我们还需要对item_layout.xml进行更改:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:textSize="40sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
LinearLayout>
再运行试试:
因为显示原因,看起来子项滑动的时候分割线好像断了一样,实际上并没有这个bug。ok,下面来试试网格布局:
在MainActivity中加了一个网格布局,并且设置排布方向和每一行显示的子项数,之后调用了MyItemDecoration的第二个适用于网格布局的构造方法。为了美观,我们还得对item_layout.xml文件进行改动:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/textView"
android:textSize="40sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
LinearLayout>
来看看效果:
ok, 最后,来看一下网格布局的横向排布方式的效果,MainActivity.java 只需要改动一个网格布局的排布方向就行了,这里就不贴了,之后为了美观还得改一下item_layout.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:layout_width="wrap_content"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:textSize="40sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
LinearLayout>
好了,RecyclerView控件的分隔线就介绍的差不多了,RecyclerView控件相当于ListView控件的升级版,使用步骤也类似:定义控件、使用适配器添加数据、添加布局管理器、添加分隔线、添加动画效果等等,正是因为这种完全的解耦机制才成就了它的灵活性。我们可以通过自己的需求来定义效果。
如果博客中有什么不正确的地方,还请多多指点。
谢谢观看。。。