在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, 效果图:
2. 最后一项的divider
很多同学可能发现了,listview最后一项的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状态:
通过分析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状态如下:
界面中group_container那块由之前的绿色变成了蓝色,说明减少了一次overdraw。
上述情况下,spacedividerview解耦了背景色,优化了overdraw,而且这个spacedividerview也是支持4个方向的,使用起来特别方便。
阴影divider
阴影分割线的特点是重叠在下面的view之上的,它的目的是一种分割线的立体效果。
使用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,希望对大家有所帮助!