谈谈Android中的Divider是个什么东东

时间:2022-06-22 06:46:09

在android应用开发中会经常碰到一个叫divider的东西,就是两个view之间的分割线。最近工作中注意到这个divider并分析了一下,竟然发现内有乾坤,惊为天人…

listview的divider

1. 定制divider的边距

listview的divider默认是左右两头到底的,如何简单的设置一个边距呢?

利用inset或者layer-list都可以简单的实现,代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 方法一 -->
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:insetleft="16dp" >
<shape android:shape="rectangle" >
<solid android:color="#f00" />
</shape>
</inset>
<!-- 方法二 -->
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:left="16dp">
<shape android:shape="rectangle">
<solid android:color="#f00" />
</shape>
</item>
</layer-list>

其中inset除了左边距insetleft, 还有insettop、insetright、insetbottom, 效果图:

谈谈Android中的Divider是个什么东东

2. 最后一项的divider

很多同学可能发现了,listview最后一项的divider有时候有,有时候又没有。

我画个图大家就都能理解了:

谈谈Android中的Divider是个什么东东

上面是数据不足的显示效果,如果数据满屏的话,都是看不多最后的divider的。

真相是,当listview高度是不算最后一项divider的,所以只有在match_parent的情况下,listview的高度是有余的,才能画出最后的那个divider。

ps:网上很多资料,把最后一项的divider和footerdividersenabled混在一起了,这个是不对的,两个从逻辑上是独立的,类似的还有一个headerdividersenabled,headerdividersenabled和footerdividersenabled不会影响到默认情况下最后的divider的绘制,他们是给header和footer专用的,特此说明。

recyclerview的divider

recyclerview的divider叫做itemdecoration,recyclerview.itemdecoration本身是一个抽象类,官方没有提供默认实现。

官方的support7demos例子中有个divideritemdecoration, 我们可以直接参考一下,位置在sdk的这里:

extras/android/support/samples/support7demos/src/…/…/decorator/divideritemdecoration.java

但是这个divideritemdecoration有三个问题:

只支持系统默认样式,不支持自定义drawable类型的divider

里面的算法对于无高宽的drawable(比如上面用到的insetdrawable)是画不出东西的水平列表的divider绘制方法drawhorizontal()的right计算有误,导致垂直divider会绘制不出来,应该改为:final int right = left + mdivider.getintrinsicwidth();;

针对这几个问题,我修复并增强了一下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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.v4.view.viewcompat;
import android.support.v7.widget.linearlayoutmanager;
import android.support.v7.widget.recyclerview;
import android.view.view;
/**
* recyclerview的itemdecoration的默认实现
* 1. 默认使用系统的分割线
* 2. 支持自定义drawable类型
* 3. 支持水平和垂直方向
* 4. 修复了官方垂直divider显示的bug
* 扩展自官方android sdk下的support7demos下的divideritemdecoration
*/
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 mwidth;
private int mheight;
private int morientation;
public divideritemdecoration(context context, int orientation) {
final typedarray a = context.obtainstyledattributes(attrs);
mdivider = a.getdrawable(0);
a.recycle();
setorientation(orientation);
}
/**
* 新增:支持自定义dividerdrawable
*
* @param context
* @param orientation
* @param dividerdrawable
*/
public divideritemdecoration(context context, int orientation, drawable dividerdrawable) {
mdivider = dividerdrawable;
setorientation(orientation);
}
public void setorientation(int orientation) {
if (orientation != horizontal_list && orientation != vertical_list) {
throw new illegalargumentexception("invalid orientation");
}
morientation = orientation;
}
/**
* 新增:支持手动为无高宽的drawable制定宽度
* @param width
*/
public void setwidth(int width) {
this.mwidth = width;
}
/**
* 新增:支持手动为无高宽的drawable制定高度
* @param height
*/
public void setheight(int height) {
this.mheight = height;
}
@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);
final recyclerview.layoutparams params = (recyclerview.layoutparams) child
.getlayoutparams();
final int top = child.getbottom() + params.bottommargin +
math.round(viewcompat.gettranslationy(child));
final int bottom = top + getdividerheight();
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 +
math.round(viewcompat.gettranslationx(child));
final int right = left + getdividerwidth();
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, getdividerheight());
} else {
outrect.set(0, 0, getdividerwidth(), 0);
}
}
private int getdividerwidth() {
return mwidth > 0 ? mwidth : mdivider.getintrinsicwidth();
}
private int getdividerheight() {
return mheight > 0 ? mheight : mdivider.getintrinsicheight();
}
}

使用如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 默认系统的divider
divideritemdecoration = new divideritemdecoration(this, divideritemdecoration.vertical_list);
// 自定义图片drawable分的divider
divideritemdecoration = new divideritemdecoration(this, divideritemdecoration.vertical_list, getresources().getdrawable(r.drawable.ic_launcher));
// 自定义无高宽的drawable的divider - 垂直列表
divideritemdecoration = new divideritemdecoration(this, divideritemdecoration.vertical_list, new colordrawable(color.parsecolor("#ff00ff")));
divideritemdecoration.setheight(1);
// 自定义无高宽的drawable的divider - 水平列表
divideritemdecoration = new divideritemdecoration(this, divideritemdecoration.horizontal_list, new colordrawable(color.parsecolor("#ff00ff")));
divideritemdecoration.setwidth(1);
// 自定义带边距且无高宽的drawable的divider(以上面insetdrawable为例子)
// 这个地方也可以在drawable的xml文件设置size指定宽高,效果一样
divideritemdecoration = new divideritemdecoration(this, divideritemdecoration.horizontal_list, getresources().getdrawable(r.drawable.list_divider));
divideritemdecoration.setwidth(displayless.$dp2px(16) + 1);

手动的divider

有的时候没有系统控件的原生支持,只能手动在两个view加一个divider,比如,设置界面每项之间的divider,水平平均分隔的几个view之间加一个竖的divider等等。

无论横的竖的,都非常简单,定一个view,设置一个background就可以了,正常情况下没什么好说的。

下面我们来考虑一种常见设置界面,这种设置界面的分割线是有左边距的,比如微信的设置界面,我相信绝大部分人的布局代码都是这样实现的:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--这个group_container的background一定要设置,
而且要和list_item_bg的list_item_normal一致,
否则效果会不正确。 -->
<linearlayout
android:id="@+id/group_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignparenttop="true"
android:layout_margintop="48dp"
android:background="#fff"
android:orientation="vertical">
<relativelayout
android:id="@+id/account_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_item_bg"
android:clickable="true">
<textview
android:id="@+id/account_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignparentleft="true"
android:layout_centervertical="true"
android:layout_margin="16dp"
android:text="first item"
android:textcolor="#f00"
android:textsize="16sp" />
</relativelayout>
<view
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginleft="16dp"
android:background="#f00" />
<relativelayout
android:id="@+id/phone_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_item_bg"
android:clickable="true">
<textview
android:id="@+id/phone_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignparentleft="true"
android:layout_centervertical="true"
android:layout_margin="16dp"
android:text="second item"
android:textcolor="#f00"
android:textsize="16sp" />
</relativelayout>
</linearlayout>
</relativelayout>

效果图如下,顺便我们也看看它的overdraw状态:

谈谈Android中的Divider是个什么东东

通过分析overdraw的层次,我们发现为了一个小小的边距,设置了整个groud_container的背景,从而导致了一次overdraw。

能不能优化掉这个overdraw?答案是肯定的。

背景肯定要去掉,但是这个左边距的view就不能这么简单的写了,需要自定义一个view,它要支持能把左边距的空出的16dp的线用list_item_normal的颜色值绘制一遍,这样才能看的出左边距。

这个view具体代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import android.content.context;
import android.content.res.typedarray;
import android.graphics.canvas;
import android.graphics.color;
import android.graphics.paint;
import android.util.attributeset;
import android.util.typedvalue;
import android.view.view;
import com.jayfeng.lesscode.core.r;
public class spacedividerview extends view {
private int mspaceleft = 0;
private int mspacetop = 0;
private int mspaceright = 0;
private int mspacebottom = 0;
private int mspacecolor = color.transparent;
private paint mpaint = new paint();
public spacedividerview(context context) {
this(context, null);
}
public spacedividerview(context context, attributeset attrs) {
this(context, attrs, 0);
}
public spacedividerview(context context, attributeset attrs, int defstyleattr) {
super(context, attrs, defstyleattr);
typedarray a = context.obtainstyledattributes(attrs, r.styleable.spacedividerview, defstyleattr, 0);
mspaceleft = a.getdimensionpixelsize(r.styleable.spacedividerview_spaceleft,
(int) typedvalue.applydimension(typedvalue.complex_unit_dip, 0, getresources().getdisplaymetrics()));
mspacetop = a.getdimensionpixelsize(r.styleable.spacedividerview_spacetop,
(int) typedvalue.applydimension(typedvalue.complex_unit_dip, 0, getresources().getdisplaymetrics()));
mspaceright = a.getdimensionpixelsize(r.styleable.spacedividerview_spaceright,
(int) typedvalue.applydimension(typedvalue.complex_unit_dip, 0, getresources().getdisplaymetrics()));
mspacebottom = a.getdimensionpixelsize(r.styleable.spacedividerview_spacebottom,
(int) typedvalue.applydimension(typedvalue.complex_unit_dip, 0, getresources().getdisplaymetrics()));
mspacecolor = a.getcolor(r.styleable.spacedividerview_spacecolor, color.transparent);
a.recycle();
mpaint.setcolor(mspacecolor);
}
@override
protected void ondraw(canvas canvas) {
super.ondraw(canvas);
if (mspaceleft > 0) {
canvas.drawrect(0, 0, mspaceleft, getmeasuredheight(), mpaint);
}
if (mspacetop > 0) {
canvas.drawrect(0, 0, getmeasuredwidth(), mspacetop, mpaint);
}
if (mspaceright > 0) {
canvas.drawrect(getmeasuredwidth() - mspaceright, 0, getmeasuredwidth(), getmeasuredheight(), mpaint);
}
if (mspacebottom > 0) {
canvas.drawrect(0, getmeasuredheight() - mspacebottom, getmeasuredwidth(), getmeasuredheight(), mpaint);
}
}
}

用这个spacedividerview我们重写一下上面的布局代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<linearlayout
android:id="@+id/group_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignparenttop="true"
android:layout_margintop="48dp"
android:orientation="vertical">
<relativelayout
android:id="@+id/account_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_item_bg"
android:clickable="true">
<textview
android:id="@+id/account_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignparentleft="true"
android:layout_centervertical="true"
android:layout_margin="16dp"
android:text="first item"
android:textcolor="#f00"
android:textsize="16sp" />
</relativelayout>
<com.jayfeng.lesscode.core.other.spacedividerview
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#f00"
app:spaceleft="16dp"
app:spacecolor="@color/list_item_normal"/>
<relativelayout
android:id="@+id/phone_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_item_bg"
android:clickable="true">
<textview
android:id="@+id/phone_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignparentleft="true"
android:layout_centervertical="true"
android:layout_margin="16dp"
android:text="second item"
android:textcolor="#f00"
android:textsize="16sp" />
</relativelayout>
</linearlayout>
</relativelayout>

效果图和overdraw状态如下:

谈谈Android中的Divider是个什么东东

界面中group_container那块由之前的绿色变成了蓝色,说明减少了一次overdraw。

上述情况下,spacedividerview解耦了背景色,优化了overdraw,而且这个spacedividerview也是支持4个方向的,使用起来特别方便。

阴影divider

阴影分割线的特点是重叠在下面的view之上的,它的目的是一种分割线的立体效果。

谈谈Android中的Divider是个什么东东

使用relativelayout并控制上边距离可以实现:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- layout_margintop的值应该就是不包括阴影高度的header高度-->
<linearlayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignparenttop="true"
android:layout_margintop="@dimen/header_height"
android:orientation="vertical">
</linearlayout>
<!-- 这个要放在最后,才能显示在最上层,这个header里面包括一个阴影view-->
<include
android:id="@+id/header"
layout="@layout/include_header" />
</relativelayout>

虽然再简单不过了,还是稍微分析一下,header包括内容48dp和阴影8dp,那么margintop就是48dp了。

下面给大家介绍android给listview设置分割线divider样式

给listview设置分割线,只需设置如下两个属性:

android:divider="#000" //设置分割线显示颜色
android:dividerheight="1px" //此处非0,否则无效

?
1
2
3
4
5
6
<listview android:id="@+id/listview"  
 android:layout_width="fill_parent"
 android:layout_height="fill_parent"  
 android:divider="#fff"
 android:dividerheight="1px"
 android:layout_margin="10dip"/>

以上内容给大家简单介绍了android中的divider,希望对大家有所帮助!