ListView的divider为何无法显示?究竟是道德沦丧,还是...





案前提要

虽然由于RecycleView的出现,ListView已经较少的被使用,但是在维护项目之前的代码时,难免还是会遇到需要对相关ListView的Adapter做调整的情况,这次记录的,就是在UI改版时,调整列表视图所遇到的一个小问题——divider无法显示。

案情概览

在APP改版项目中,有一个列表界面的UI改动,其中就包括了对分割线——也就是divider——的样式改动,团队中的一个小伙伴发现之前的divider效果,是在每个item下面加了一个View并设置背景实现的,使得这个item徒增了一层LinearLayout,于是想趁着改版一并优化一下,直接使用ListView的divider。然后。。。天有不测风云。。。divider居然。。。出!不!来!!!更奇怪的是,改变dividerHeight每一项之间的间隔是会改变的,也就是说dividerHeight是有效的,但是divider就是不显示。

于是,这位小伙伴找到我,让我看一看。既然问题被交到我这了,那自然只有去解决了。

案情分析

拿到问题后,我前前后后看了看代码,自然也是去百度Google了一翻,仍没有找到答案,那么只有遵循程序员第一原则了——Reading the Fucking Source Code!!!

于是,我进入到ListView的源码中去寻找答案,去看看ListView对divider的绘制究竟是怎么执行的。下面是dispatchDraw相关代码,省去与divider无关代码:

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
@Override
protected void dispatchDraw(Canvas canvas) {
...
//这句很好理解
final boolean drawDividers = dividerHeight > 0 && mDivider != null;
//我们这次不关心OverscrollHeader和OverscrollFooter
if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
...
final int count = getChildCount();
...
final int scrollY = mScrollY;
//如果是从上到下的填充顺序
if (!mStackFromBottom) {
//第一次判断,绘制顶端divider,只是单纯判断是否需要绘制divider
if (count > 0 && scrollY < 0) {
if (drawOverscrollHeader) {
...
} else if (drawDividers) {
bounds.bottom = 0;
bounds.top = -dividerHeight;
drawDivider(canvas, bounds, -1);
}
}
//遍历子视图
for (int i = 0; i < count; i++) {
...
//如果展示headerDivider、footerDivider
//或者当前不是header、footer,即item
if ((headerDividers || !isHeader) && (footerDividers || !isFooter)) {
...
//主要还是drawDivider判断
//剩下的只是保证当前不是在列表最后一项以及不用绘制OverscrollFooter
if (drawDividers && (bottom < listBottom)
&& !(drawOverscrollFooter && isLastItem)) {
...
//关键的判断,前方高能!!!
//如果adapter中设置的当前项是enable的
//并且(要么绘制headerDivider,要么只是普通item)
//并且(要么是最后一项,要么adapter中下一项是enable的,
//要么绘制footerDivider)。。。。。。
//简单来说,就是只有在两个enable的item之间,才会绘制divider
if (adapter.isEnabled(itemIndex) &&
(headerDividers || !isHeader && (nextIndex >= headerCount)) &&
(isLastItem || adapter.isEnabled(nextIndex) &&
(footerDividers || !isFooter &&
(nextIndex < footerLimit)))) {
bounds.top = bottom;
bounds.bottom = bottom + dividerHeight;
drawDivider(canvas, bounds, i);
}else if(...){...}
}
}
}
}else {//从下往上的情况只是判断方式有区别,不再详述
...
}
}
}

有点晕= =。。。总的来说,就是只有当两个item都是可用的情况下,才会在他们之间绘制divider,而这个是否可用,就是由adapter的isEnable方法返回的:

1
2
3
4
5
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
public boolean isEnabled(int position) {
return true;
}
}

默认情况下,该方法是返回true的。

真相大白

到这里,似乎就明朗了,如果对于divider的配置都是正确的话,那么外部还能影响每个item之间divider展示的,就只有isEnable方法了。

到相关的adapter里一看,果不其然,在某些情况下,返回了false。那么为什么要这么写呢,原来这个isEnable还有另外一个作用,我们再来看看源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class AbsListView....{
private void setItemViewLayoutParams(View child, int position) {
...
lp.isEnabled = mAdapter.isEnabled(position);
...
}
private void onTouchUp(MotionEvent ev) {
...
else if (!mDataChanged && mAdapter.isEnabled(motionPosition)) {
performClick.run();
}
}
}

看完这些相信大家也明白了,在相应position返回fasle的情况下,该位置对应的item点击是无效的,当初的某位小伙伴就是为了屏蔽某些情况下的点击事件,才这么写了,或许当时也是发现了divider的问题,才在item的视图中加了一个view做为divider的代替。

案情回顾

最后,总结一下:

1、Adapter的isEnable方法,可以决定对应位置的item是否可以被点击;

2、只有在两个item对应的position在Adapter中isEnable方法返回为true的时候,才会在这两个item之间绘制divider。