逐步实现ListView嵌套ExpandableLayoutListView

来源:互联网 发布:intent传递数据 编辑:程序博客网 时间:2024/06/17 23:08

最近在尝试做一个多层嵌套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;    }
  • 子列表中展示具体的记录
@Overridepublic 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