首页 > 解决方案 > 如何将粘性标题添加到列表视图?

问题描述

我想在不使用第三方库的情况下将粘性标题添加到列表视图。我已经为. grouped list view_ )? 有没有更好的方法来做到这一点?stacklist viewwidget

class GroupedListView<E, G extends Comparable<Object>> extends StatefulWidget {
  const GroupedListView({
    @required this.groupBy,
    @required this.groupBuilder,
    @required this.itemBuilder,
    Key key,
    this.elements,
  }) : super(key: key);

  final G Function(E element) groupBy;
  final Widget Function(G group) groupBuilder;
  final Widget Function(BuildContext context, E element) itemBuilder;
  final List<E> elements;

  @override
  _GroupedListViewState<E, G> createState() => _GroupedListViewState<E, G>();
}

class _GroupedListViewState<E, G extends Comparable<Object>>
    extends State<GroupedListView<E, G>> {
  List<E> _elements;

  @override
  void initState() {
    super.initState();

    _elements = widget.elements;
  }

  @override
  Widget build(BuildContext context) => Stack(
        children: <Widget>[
          ListView.builder(
            itemCount: _elements.length * 2,
            itemBuilder: (BuildContext context, int index) {
              final int actualIndex = index ~/ 2;

              if (index.isEven) {
                final G currentGroup = widget.groupBy(_elements[actualIndex]);
                final G previousGroup = actualIndex - 1 < 0
                    ? null
                    : widget.groupBy(_elements[actualIndex - 1]);

                if (previousGroup != currentGroup) {
                  return widget.groupBuilder(currentGroup);
                }

                return Container();
              }

              return widget.itemBuilder(context, _elements[actualIndex]);
            },
          ),
          // THIS WIDGET SHOULD REBUILD BY LISTENING TO SCROLL VIEW CONTROLLER
          Container(
            color: Colors.black,
            child: ListTile(
              title: Text(
                'STICKY HEADER',
                style: TextStyle(color: Colors.white),
              ),
            ),
          ),
        ],
      );
}

示例代码:

class HomePage extends StatefulWidget {
  HomePage({
    Key key,
    this.title,
  }) : super(key: key);

  final String title;

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: GroupedListView<dynamic, String>(
        elements: listItems,
        groupBy: (dynamic element) => element['group'],
        groupBuilder: (String value) => Container(
          color: Colors.grey,
          child: ListTile(
            title: Text(
              value,
              style: TextStyle(color: Colors.white),
            ),
          ),
        ),
        itemBuilder: (BuildContext context, dynamic element) => ListTile(
          title: Text(element['name']),
        ),
      ),
    );
  }
}

final List<Map<String, String>> listItems = <Map<String, String>>[
  <String, String>{'name': 'user_01', 'group': 'group_01'},
  <String, String>{'name': 'user_02', 'group': 'group_01'},
  <String, String>{'name': 'user_03', 'group': 'group_02'},
  <String, String>{'name': 'user_04', 'group': 'group_02'},
  <String, String>{'name': 'user_05', 'group': 'group_02'},
  <String, String>{'name': 'user_06', 'group': 'group_03'},
  <String, String>{'name': 'user_07', 'group': 'group_04'},
  <String, String>{'name': 'user_08', 'group': 'group_04'},
  <String, String>{'name': 'user_09', 'group': 'group_05'},
  <String, String>{'name': 'user_10', 'group': 'group_05'},
  <String, String>{'name': 'user_11', 'group': 'group_06'},
  <String, String>{'name': 'user_12', 'group': 'group_06'},
];

任何帮助将不胜感激。

标签: flutterlistview

解决方案


是的,您可以使用 aStack来实现此行为。首先,您需要做一些数学运算来获得每个组标题行的高度。然后你需要给滚动控制器添加一个监听器。此侦听器检查滚动控制器的当前偏移量是否在与组相关的区域中,它将current变量设置为组名列表中该组的索引。separatorHeight是组标题行的高度。tileHeight是项目行的高度。

import 'package:flutter/material.dart';
import 'package:collection/collection.dart';

class GroupedListView<E, G extends Comparable<Object>> extends StatefulWidget {
  const GroupedListView({
    @required this.groupBy,
    @required this.groupBuilder,
    @required this.itemBuilder,
    @required this.separatorHeight,
    @required this.tileHeight,
    Key key,
    this.elements,
  }) : super(key: key);

  final G Function(E element) groupBy;
  final Widget Function(G group) groupBuilder;
  final Widget Function(BuildContext context, E element) itemBuilder;
  final List<E> elements;
  final double separatorHeight;
  final double tileHeight;

  @override
  _GroupedListViewState<E, G> createState() => _GroupedListViewState<E, G>();
}

class _GroupedListViewState<E, G extends Comparable<Object>>
    extends State<GroupedListView<E, G>> {
  List<E> _elements;

  List<double> _groupHeights;
  List<G> _groupNames;
  int _currentGroupIndex = 0;

  final ScrollController _scrollController = ScrollController();

  @override
  void initState() {
    super.initState();

    _elements = widget.elements;

    _groupNames = groupBy<E, G>(_elements, widget.groupBy)
        .entries
        .map<G>((dynamic entry) => entry.key)
        .toList();

    _groupHeights = groupBy<E, G>(_elements, widget.groupBy)
        .entries
        .map<double>((dynamic entry) => entry.value.length.toDouble())
        .toList();

    double sum = 0;

    _groupHeights = _groupHeights
        .map<double>((double itemCount) =>
            sum += itemCount * widget.tileHeight + widget.separatorHeight)
        .toList();

    _scrollController.addListener(() {
      final double controllerOffset =
          _scrollController.offset + widget.separatorHeight;

      if (controllerOffset < _groupHeights.first) {
        setState(() => _current = 0);
      } else {
        for (int i = 1; i < _groupHeights.length; i++) {
          if (controllerOffset >= _groupHeights[i - 1] &&
              controllerOffset < _groupHeights[i]) {
            setState(() => _current = i);

            break;
          }
        }
      }
    });
  }

  @override
  Widget build(BuildContext context) => Stack(
        children: <Widget>[
          Container(
            margin: EdgeInsets.only(top: widget.separatorHeight),
          ),
          ListView.builder(
            key: widget.key,
            controller: _scrollController,
            itemCount: _elements.length * 2,
            itemBuilder: (BuildContext context, int index) {
              final int actualIndex = index ~/ 2;

              if (index.isEven) {
                final G currentGroup = widget.groupBy(_elements[actualIndex]);
                final G previousGroup = actualIndex - 1 < 0
                    ? null
                    : widget.groupBy(_elements[actualIndex - 1]);

                if (previousGroup != currentGroup) {
                  return SizedBox(
                    height: widget.separatorHeight,
                    child: widget.groupBuilder(currentGroup),
                  );
                }

                return Container();
              }

              return SizedBox(
                height: widget.tileHeight,
                child: widget.itemBuilder(context, _elements[actualIndex]),
              );
            },
          ),
          SizedBox(
            height: widget.separatorHeight,
            child: widget.groupBuilder(_groupNames[_currentGroupIndex]),
          ),
        ],
      );
}

结果:

资源


推荐阅读