ExpandableListView点击最后一个child item不回调onChildClick()的问题

本文深入探讨了在Android4.3及以下系统下,ExpandableListView及其子类控件中通过setOnChildClickListener设置的监听器无法监听到最后一个childitem点击事件的原因,并最终定位到问题在于系统计算footerViewsStart时忽略了FooterView的数量导致误判。文中提供了解决方案,即通过自定义监听机制替代原始的OnChildClickListener实现需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

遇到的问题:

在使用ExpandableListView及其子类控件时,如果你通过调用setOnChildClickListener(OnChildClickListener onChildClickListener)方法去设置监听器去监听child item的点击事件,会发现在Android 4.3(包括4.3)以下的系统的手机上,这个监听器无法监听到最后一个child item的点击事件。

问题根源:

遇到这个问题的时候,最初始的猜想是ExapandableListView在回调OnChildClickListener的onChildClick()方法之前把最后一个child item的点击事件给截住了。但到底为什么会被截住了,那肯定是要去看源码了。
然后发现是在ExapandableListView类中的handleItemClick()方法中回调onChildClick()方法:

    /**
     * This will either expand/collapse groups (if a group was clicked) or pass
     * on the click to the proper child (if a child was clicked)
     * 
     * @param position The flat list position. This has already been factored to
     *            remove the header/footer.
     * @param id The ListAdapter ID, not the group or child ID.
     */
    boolean handleItemClick(View v, int position, long id) {
        final PositionMetadata posMetadata = mConnector.getUnflattenedPos(position);

        id = getChildOrGroupId(posMetadata.position);

        boolean returnValue;
        if (posMetadata.position.type == ExpandableListPosition.GROUP) {
            /* It's a group, so handle collapsing/expanding */

            /* It's a group click, so pass on event */
            ......
            ......
            returnValue = true;
        } else {
            /* It's a child, so pass on event */
            if (mOnChildClickListener != null) {
                playSoundEffect(SoundEffectConstants.CLICK);
                return mOnChildClickListener.onChildClick(this, v, posMetadata.position.groupPos,
                        posMetadata.position.childPos, id);
            }

            returnValue = false;
        }

        posMetadata.recycle();

        return returnValue;
    }

这个方法里面看不出什么毛病,都是根据被点击的item的position判断这个item是group item还是child item,再调用对应的回调方法。那既然这里看不出什么毛病,就再去看看调用这个handleItemClick()的地方看看。搜索handleItemClick,发现这个方法是在performItemClick()方法中被调用:

    @Override
    public boolean performItemClick(View v, int position, long id) {
        // Ignore clicks in header/footers
        if (isHeaderOrFooterPosition(position)) {
            // Clicked on a header/footer, so ignore pass it on to super
            return super.performItemClick(v, position, id);
        }

        // Internally handle the item click
        final int adjustedPosition = getFlatPositionForConnector(position);
        return handleItemClick(v, adjustedPosition, id);
    }

咦,这里可以看出一些端倪了,这里在调用handleItemClick()方法之前,会根据被点击的item的position判断这个item是否Header或者Footer,如果是,就不会调用handleItemClick()方法,那也自然不会回调OnChildClickListener的onChildClick()方法。那究竟问题是不是出现在这里呢,让我们继续跟进去判断是否Header或者Footer的方法isHeaderOrFooterPosition()中看看是怎么判断的:

    /**
     * @param position An absolute (including header and footer) flat list position.
     * @return true if the position corresponds to a header or a footer item.
     */
    private boolean isHeaderOrFooterPosition(int position) {
        // 这里先算出FooterView的开始位置,注意这里的mItemCount变量是在其父类ListView中定义的
        // 而mItemCount的值其实就等于mAdapter.getCount()
        final int footerViewsStart = mItemCount - getFooterViewsCount();
        // 这里就开始判断这个position是否Header或者Footer
        return (position < getHeaderViewsCount() || position >= footerViewsStart);
    }

那既然现在是最后一个child item出现问题,那此时可以大胆猜测问题可能出在这个footerViewsStart上,那想要证实我们的猜测,那就把这个变量打印出来看看就好了。想要打印这个变量,我的方法是写一个类继承ExpandableListView,然后重写performItemClick(),在该方法中模仿isHeaderOrFooterPosition()方法中的footerViewsStart的计算方式,模拟出footerViewsStart的值,然后打印到logcat上:

public class TestExpandableListView extends ExpandableListView {

    private final static String TAG = "TestExpandableListView";

    public TestExpandableListView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean performItemClick(View v, int position, long id) {
        Log.d(TAG, "getAdapter().getCount() = " + getAdapter().getCount());
        Log.d(TAG, "getFooterViewsCount() = " + getFooterViewsCount());

        return super.performItemClick(v, position, id);
    }
}

通过查看logcat发现,如果ExpandableListView有FooterView的话(即getFooterViewsCount()返回的值大于0),在Android 4.3(包括4.3)以下的系统的getAdapter().getCount()返回的值是没有把FooterView的数量给加上,而Android 4.3以上的则会加上。这就直接导致了在有FooterView的情况下,Android 4.3(包括4.3)以下的系统把最后一个child item当成是FooterView导致没有回调OnChildClickListener的onChildClick()方法的问题了。

到这里问题根源终于找到了,google也是发现了这个bug了,所以在Android 4.4.4版本就已经把这个问题给修复了。

如何解决

问题找到了,那接下来该如何去解决呢?
我的解决方法其实是很粗暴,但也很简单,就是放弃使用OnChildClickListener监听器,而在Adapter中通过设置child item的convertView的onClik事件来监听每个child item的点击。

如果大家有什么更好的方法欢迎各位热烈评论交流。

多谢各位客官花时间帮衬本文章,本yeah目前是Android菜鸟,欢迎转载文章,完我的明星梦,打字不易,转载时请注明出处。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值