flutter 如何动态锚列表顶部或底部的项目?

eulz3vhy  于 2023-03-19  发布在  Flutter
关注(0)|答案(2)|浏览(169)

bounty将在5天后过期。此问题的答案可获得+50声望奖励。Ruble正在寻找此问题的更详细答案:根据元素在列表中的位置,有必要将预先选定的元素自动固定到屏幕(或ListView)的底部或顶部。

我们怎么能做出这种列表行为呢?
作为输入参数,我们有
1.待修复元素的索引(必要时可提前找到)
1.项目表
我可以想象出一种方法来修复顶部的元素,尽管这种方法令人沮丧:

final indexPinnedItem = 23;

final listBefore = [...all elements up to 23];
final listAfter = [...all elements after 23];

return CustomScrollView(
      slivers: [
        SliverList(
          delegate: SliverChildBuilderDelegate(
            (BuildContext context, int index) {
              return listBefore; // elements with index from 0 to 22 inclusive
            }),
        ),
        SliverPinnedHeader(
          child: // element with index 23,
        ),
        SliverList(
          delegate: SliverChildBuilderDelegate(
            (BuildContext context, int index) {
              return listAfter; // elements with index from 24 to
            }),
        ),
      ],
    );

注意元素的高度可以不同。固定元素和“自由浮动”元素的大小是相同的(事实上,它们是同一个小部件。我不需要它在一个项目固定后就开始改变大小、形状或其他任何东西)。
如何实现这种行为?

更新日期:

整个神圣的一点是,我想看到大列表中当前选中的项目,它不完全适合屏幕。

  • 〉假设所选项目的序号为23。当我们转到屏幕时,只能看到项目1到4。因此,必须从下方固定第23个元素(图1)。
  • 〉现在我们向下滚动到23个项目,它会自动分离,并在列表中的任何地方可见(图2)。
  • 〉但是,一旦一个项目超出了视图,它会自动重新锁定(在底部或顶部,取决于我们当时滚动的位置)(图3,4)。
gopyfrb3

gopyfrb31#

这里是编辑后的解决方案,你提供了更多的信息,你正在努力实现。
对于Pinned磁贴,为了简单起见,我使用了与您相同的包,但是pinned效果很容易实现,只需将CustomScrollView Package 在Stack小部件中,然后使用Positioned小部件创建一个顶部粘性磁贴。
其逻辑是创建一个Class,它有一个isPinned属性,并且mapping所有的list elements都是这样一个类。
现在,您将创建两个实用程序methods,它们将循环遍历列表,并设置/取消设置固定状态。
在下面的工作示例中,我只实现了top pinned特性,因为在顶部或底部创建一个动态固定要复杂得多,为此,我强烈建议您首先使用pinned top的唯一方法,使用干净的代码来实现它,然后如果您真的想扩展它,您应该创建一个关于它的新问题。
在下面的例子中,当你把一个小部件设置为pinned时,只有当它不在视图中时,它才会被固定。为了检测可见性,我使用了一个非常好的、受支持的包visibility_detector,由Google团队维护。

这是将Map到您的列表的自定义Class

class TileData {
  final int id;
  final String title;
  bool isPinned;

  TileData({required this.id, required this.title, this.isPinned = false});
}

这是您的view,显示所有UI:

class ListTest2 extends StatefulWidget {
  ListTest2();

  @override
  State<ListTest2> createState() => _ListTest2State();
}

class _ListTest2State extends State<ListTest2> {
  late final List<TileData> listData;
  int? pinnedWidgetId;
  bool showPinned = false;

  @override
  void initState() {
    listData = List.generate(40, (index) => TileData(id: index, title: "Hello $index"));
    super.initState();
  }

  void pinItem(int id) {
    listData.forEach((e) {
      e.isPinned = false;

      if (e.id == id) {
        e.isPinned = true;
        pinnedWidgetId = e.id;
      }
    });
  }

  void unpin(int id) {
    listData.firstWhereOrNull((e) => e.id == id)?.isPinned = false;
    pinnedWidgetId = null;
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        CustomScrollView(
          slivers: [
            if (pinnedWidgetId != null && showPinned)
              SliverPinnedHeader(
                child: Container(
                  color: Colors.white,
                  child: CustomListTile(
                    data: listData[pinnedWidgetId!],
                    isPinnedDisplayed: showPinned,
                    onPressed: () => setState(() {
                      listData[pinnedWidgetId!].isPinned ? unpin(listData[pinnedWidgetId!].id) : pinItem(listData[pinnedWidgetId!].id);
                    }),
                    isPinnedTile: true,
                  ),
                ),
              ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  return VisibilityDetector(
                    key: Key("${listData[index].id}"),
                    onVisibilityChanged: (visibilityInfo) {
                      if (listData[index].isPinned) {
                        if (visibilityInfo.visibleFraction == 0) {
                          setState(() {
                            showPinned = true;
                          });
                        } else if (visibilityInfo.visibleFraction != 0 && showPinned) {
                          setState(() {
                            showPinned = false;
                          });
                        }
                      }
                    },
                    child: CustomListTile(
                      data: listData[index],
                      isPinnedDisplayed: showPinned,
                      onPressed: () => setState(() {
                        listData[index].isPinned ? unpin(listData[index].id) : pinItem(listData[index].id);
                      }),
                    ),
                  );
                },
                childCount: listData.length,
              ),
            ),
          ],
        ),
      ],
    );
  }
}

最后,这是ListTile自定义小部件,我在单独的class中提取了它:

class CustomListTile extends StatelessWidget {
  final TileData data;
  final void Function()? onPressed;
  final bool isPinnedTile;
  final bool isPinnedDisplayed;

  CustomListTile({required this.data, this.onPressed, this.isPinnedTile = false, required this.isPinnedDisplayed});

  @override
  Widget build(BuildContext context) {
    return !isPinnedTile && data.isPinned && isPinnedDisplayed
        ? const SizedBox(
            height: 1,
          )
        : Padding(
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
            child: Container(
              height: 75,
              decoration: BoxDecoration(border: Border.all(color: Colors.black), color: Colors.white),
              child: ListTile(
                title: Text(data.title),
                leading: Container(
                  width: 100,
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      IconButton(
                        icon: Icon(
                          Icons.star,
                          color: data.isPinned ? Colors.yellow : Colors.grey,
                        ),
                        visualDensity: const VisualDensity(horizontal: -4.0, vertical: -4.0),
                        onPressed: onPressed,
                      ),
                      data.isPinned ? const Text("UNPIN") : const Text("Pin")
                    ],
                  ),
                ),
              ),
            ),
          );
  }
}

有任何问题请随时提出!

cwdobuhd

cwdobuhd2#

在我看来,您所描述的内容最好通过Stack和Align小部件来实现。也就是说,将ListView(或等效的小部件) Package 在Stack中,然后将选中项的副本放置在Align中,并将Align放在Stack中ListView的 * 后面 *。类似于以下操作:

Stack(
  children: [
        ListView(<your stuff here>),
        Align(
           alignment: Alignment.bottomLeft,
           child: <selected item>,
        ),
    ],
);

如果执行正确,结果应该是一个呈现在列表上方的项,可以使用您喜欢的对齐方式。列表将在项的后面上下滚动,并且项始终可见。请确保使用shrinkWrap: true或等效项,否则高度将是无限的。
对齐:https://api.flutter.dev/flutter/widgets/Align-class.html
堆叠:https://api.flutter.dev/flutter/widgets/Stack-class.html

相关问题