Adapter 中 isEnable 方法对 ListView 的影响

941 阅读4分钟
原文链接: bakerjq.com

案前提要

虽然由于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无关代码:

@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方法返回的:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
    public boolean isEnabled(int position) {
        return true;
    }
}

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

真相大白

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

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

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。