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

遇到的问题:

在使用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菜鸟,欢迎转载文章,完我的明星梦,打字不易,转载时请注明出处。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,以下是一个 ExpandableListView 二级分栏的例子: 首先在布局文件中定义 ExpandableListView 和 Adapter: ```xml <ExpandableListView android:id="@+id/expandableListView" android:layout_width="match_parent" android:layout_height="match_parent"/> <!-- 定义自定义的 ExpandableListAdapter --> ``` 然后在 Activity 中设置 Adapter 并为 ExpandableListView 设置点击事件: ```java public class MainActivity extends AppCompatActivity { private ExpandableListView expandableListView; private ExpandableListAdapter adapter; private List<String> listDataHeader; private HashMap<String, List<String>> listDataChild; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 准备数据 prepareListData(); // 设置 Adapter adapter = new ExpandableListAdapter(this, listDataHeader, listDataChild); expandableListView.setAdapter(adapter); // 设置点击事件 expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() { @Override public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) { Toast.makeText(getApplicationContext(), listDataHeader.get(groupPosition) + " : " + listDataChild.get(listDataHeader.get(groupPosition)).get(childPosition), Toast.LENGTH_SHORT).show(); return false; } }); } // 准备数据 private void prepareListData() { listDataHeader = new ArrayList<>(); listDataChild = new HashMap<>(); // 添加一级分栏 listDataHeader.add("Fruits"); listDataHeader.add("Vegetables"); // 添加二级分栏 List<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); fruits.add("Mango"); List<String> vegetables = new ArrayList<>(); vegetables.add("Carrot"); vegetables.add("Tomato"); vegetables.add("Potato"); // 将二级分栏添加到对应的一级分栏下 listDataChild.put(listDataHeader.get(0), fruits); listDataChild.put(listDataHeader.get(1), vegetables); } } ``` 最后是 ExpandableListAdapter 的实现: ```java public class ExpandableListAdapter extends BaseExpandableListAdapter { private Context context; private List<String> listDataHeader; private HashMap<String, List<String>> listDataChild; public ExpandableListAdapter(Context context, List<String> listDataHeader, HashMap<String, List<String>> listDataChild) { this.context = context; this.listDataHeader = listDataHeader; this.listDataChild = listDataChild; } @Override public int getGroupCount() { return this.listDataHeader.size(); } @Override public int getChildrenCount(int groupPosition) { return this.listDataChild.get(this.listDataHeader.get(groupPosition)).size(); } @Override public Object getGroup(int groupPosition) { return this.listDataHeader.get(groupPosition); } @Override public Object getChild(int groupPosition, int childPosition) { return this.listDataChild.get(this.listDataHeader.get(groupPosition)).get(childPosition); } @Override public long getGroupId(int groupPosition) { return groupPosition; } @Override public long getChildId(int groupPosition, int childPosition) { return childPosition; } @Override public boolean hasStableIds() { return false; } @Override public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) { String headerTitle = (String) getGroup(groupPosition); if (convertView == null) { LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.list_group, null); } TextView lblListHeader = convertView.findViewById(R.id.lblListHeader); lblListHeader.setTypeface(null, Typeface.BOLD); lblListHeader.setText(headerTitle); return convertView; } @Override public View getChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) { final String childText = (String) getChild(groupPosition, childPosition); if (convertView == null) { LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.list_item, null); } TextView txtListChild = convertView.findViewById(R.id.lblListItem); txtListChild.setText(childText); return convertView; } @Override public boolean isChildSelectable(int groupPosition, int childPosition) { return true; } } ``` 其中 `list_group.xml` 和 `list_item.xml` 分别是一级分栏和二级分栏的布局文件,可以根据需要自行定义。 以上就是 ExpandableListView 二级分栏的例子,希望能对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值