逐步实现ListView嵌套ExpandableLayoutListView

最近在尝试做一个多层嵌套ListView,先根据日期划分成各个子Item,子Item内再嵌套一个ListView展示该日期下的详细列表,最后列表的具体项能够通过点击来展开、收起以聚焦用户的注意力。先上效果图:

效果图

接下来我们开始逐步实现:

  • 首先是布局文件,从内到外共有三个布局:交易记录、单日交易列表、整体交易列表。

可扩展布局用的是GitHub上大神的ExpandableLayout,Android
Studio 直接Gradle引入编译会报错,建议直接下载工程引用。

<!--交易记录-->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:expandable="http://schemas.android.com/apk/res-auto"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <xu.shallow.multi_listview.Expandable.ExpandableLayoutItem
        android:id="@+id/eli_row"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        expandable:el_contentLayout="@layout/view_data_item_content"
        expandable:el_headerLayout="@layout/view_data_item_header"/>

</LinearLayout>


<!--单日交易列表-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

    <TextView
        android:id="@+id/data_list_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_marginTop="10dp"
        android:background="@android:color/holo_red_light"
        android:gravity="center"
        android:paddingBottom="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_horizontal_margin"
        android:textColor="@android:color/white"/>

    <xu.shallow.multi_listview.Expandable.ExpandableLayoutListView
        android:id="@+id/elv_data"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/data_list_title"/>

</RelativeLayout>

<!--整体交易列表-->
<ListView
        android:id="@+id/lv_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
</ListView>
  • 布局文件完成之后我们开始生成测试数据并绑定到ListView上去
        //手动生成测试用数据
        List<TransInfo> list=makeDebugData();
        ListView lv_main = (ListView)findViewById(R.id.lv_main);
        Main_Adapter main_adapter = new Main_Adapter(MainActivity.this, list);
        lv_main.setAdapter(main_adapter);
  • 在主列表的Adapter中将列表按日期进行分类,并放入Map中

    private Context context;
    private List<TransInfo> list;
    private Map<String, List<TransInfo>> listMap;
    private String[] date_keys;

    public Main_Adapter(Context context, List<TransInfo> list) {
        this.context = context;
        this.list = list;
        date_keys = getList(list);
    }


    …

    public void getList(List<TransInfo> list) {
        if (list == null || list.size() == 0) {
            date_keys = new String[0];
            return;
        }
        listMap = new HashMap<>();
        try {
            for (TransInfo transInfo : list) {
                String time = transInfo.getOrder_time();
                SimpleDateFormat spf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault());
                Date format_date = spf.parse(time);
                spf = new SimpleDateFormat("yyyy年MM月dd日", Locale.getDefault());
                String date = spf.format(format_date);
                if (listMap.containsKey(date)) {
                    listMap.get(date).add(transInfo);
                } else {
                    List<TransInfo> tmpList = new ArrayList<>();
                    tmpList.add(transInfo);
                    listMap.put(date, tmpList);
                }
            }
            date_keys = listMap.keySet().toArray(new String[listMap.size()]);
            Arrays.sort(date_keys, Collections.reverseOrder());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    …

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (convertView == null) {
            convertView = LayoutInflater.from(context).inflate(R.layout.view_data_list_item, parent, false);
        }
        TextView data_list_title = BaseViewHolder.get(convertView, R.id.data_list_title);
        final ExpandableLayoutListView elv_data = BaseViewHolder.get(convertView, R.id.elv_data);

        data_list_title.setText(date_keys[position]);
        Data_List_Adapter adapter = new Data_List_Adapter(context, listMap.get(date_keys[position]));
        elv_data.setAdapter(adapter);
        return convertView;
    }
  • 子列表中展示具体的记录
@Override
public View getView(final int position, View convertView, final ViewGroup parent) {
    if (convertView == null) {
        convertView = LayoutInflater.from(context).inflate(
                R.layout.view_data_item, parent, false);
    }
    final ExpandableLayoutItem eli_row = BaseViewHolder.get(convertView, R.id.eli_row);
    TextView goods_name = BaseViewHolder.get(convertView, R.id.goods_name);
    TextView goods_amount = BaseViewHolder.get(convertView, R.id.goods_amount);
    TextView order_id = BaseViewHolder.get(convertView, R.id.order_id);
    TextView order_time = BaseViewHolder.get(convertView, R.id.order_time);

    goods_name.setText(list.get(position).getGoods_name());
    String amount = list.get(position).getGoods_amount();
    String fmt_amount = String.valueOf(Float.valueOf(amount) / 100);
    goods_amount.setText("¥ " + fmt_amount);
    order_id.setText(context.getString(R.string.order_id) + ":" + list.get(position).getOrder_id());
    order_time.setText(context.getString(R.string.order_time) + ":" + list.get(position).getOrder_time());
    return convertView;
}

好布局和代码都写好了,让我们策马崩腾跑起来~
运行效果

我们生成的测试数据是3组每组10个,运行下来却发现每组只显示了一个,查了下资料发现默认情况下Android是禁止在ScrollView中放入另外的ScrollView的,它的高度是无法计算的。为了解决这个问题,我们需要根据ListView的子项目重新计算主ListView的高度,然后把高度再作为LayoutParams设置给主ListView,这样它的高度就正确了。

public static void setListViewHeightBasedOnChildren(ListView listView) {
    try {
        ListAdapter listAdapter = listView.getAdapter();
        if (listAdapter == null) {
            // pre-condition
            return;
        }
        int totalHeight = 0;
        for (int i = 0; i < listAdapter.getCount(); i++) {
            View listItem = listAdapter.getView(i, null, listView);
            listItem.measure(0, 0);
            totalHeight += listItem.getMeasuredHeight();
        }
        ViewGroup.LayoutParams params = listView.getLayoutParams();
        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
        listView.setLayoutParams(params);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

只要在设置ListView的Adapter后调用此静态方法即可让子ListView正确的显示在其父ListView的ListItem中。但是要注意的是,子ListView的每个Item必须是LinearLayout,不能是其他的,因为其他的Layout(如RelativeLayout)没有重写onMeasure(),所以会在onMeasure()时抛出异常。
配置好后我们再来看下效果:
mov1

我们发现子ListView已经可以正常展现出来并可以点击展开具体的项,但有一个问题,就是当我们子项展开的时候,ListView的高度并没有变化,需要下拉才能实现,会导致整个界面会有两个下拉列表,对用户体验并不是很好。那么我们需要重新处理下ListView的高度计算,对于屏幕之外的Item直接通过getView来计算高度,对于展示在屏幕内的Item我们实时计算目前的高度。

if (i >= listView.getFirstVisiblePosition() && i <= listView.getLastVisiblePosition()) {
    totalHeight += listView.getChildAt(i).getMeasuredHeight();
} else {
    View listItem = listAdapter.getView(i, null, listView);
    listItem.measure(0, 0);
    totalHeight += listItem.getMeasuredHeight();
}

同时,我们需要在Item内添加一个监听器,在展开及收起的时候通知ListView高度已经改变了。

eli_row.setOnExpendItemChangeListener(new ExpandableLayoutItem.onExpendItemChangeListener() {
    @Override
    public void onChange(boolean b) {
        Util.setListViewHeightBasedOnChildren((ListView) parent);
    }
});

至此整个ListView的基本做好了,后续还需要完善的是在子Listview边界进行切换时,界面会重新计算高度,滚动条和界面会有一个短暂的卡顿。

最后附上项目地址:Multi_ListView

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值