如何在 Flutter 中的 SliverAppBar 上叠加可滚动的 Sliver?

goo*_*ser 5 scroll dart flutter flutter-sliver flutter-sliverappbar

我有一个CustomScrollViewSliverAppBar一些条子。我想在上面覆盖一些图形,SliverAppBar并让它们与列表的其余部分一起滚动。

这是我一直在使用的代码:

import 'package:flutter/material.dart';

void main() async {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: SliverTest(),
    );
  }
}

class SliverTest extends StatefulWidget {
  const SliverTest({super.key});

  @override
  State<SliverTest> createState() => _SliverTestState();
}

class _SliverTestState extends State<SliverTest> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: CustomScrollView(
          slivers: [
            SliverAppBar(
              expandedHeight: 350.0,
              floating: false,
              pinned: true,
              flexibleSpace: FlexibleSpaceBar(
                background: Container(
                  color: Colors.yellow,
                  child: const Center(child: Text('My Parallax Image!')),
                ),
              ),
            ),
            SliverToBoxAdapter(
              child: Container(
                height: 100,
                color: Colors.blueAccent,
                child: Stack(
                  children: [
                    Align(
                      alignment: const Alignment(0.0, -2.0),
                      child: Container(
                        width: 50,
                        height: 50,
                        decoration: const BoxDecoration(
                          shape: BoxShape.circle,
                          color: Colors.red,
                        ),
                        child: const Center(child: Text('Red')),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  return Container(
                    height: 50,
                    color: Colors.teal[100 * ((index % 8) + 1)],
                    child: Center(child: Text('Item #$index')),
                  );
                },
                childCount: 20,
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

在此设置中,我希望将红色圆圈(当前已裁剪)绘制在黄色部分的顶部SliverAppBar(请注意,黄色部分只是一些具有视差效果的实际图像的简单占位符,并且使用纯黄色只是为了简单起见)。我将红色圆圈放在一个条子内,因为我希望当用户与可滚动区域交互时它与列表的其余部分一起滚动。

谁能提供一个简单的例子来说明如何在 Flutter 中实现这一点?我对使用条子的任何其他解决方案持开放态度,因为它们为我的真实应用程序的其他部分提供了巨大的便利。否则,我知道我可以在不使用碎片的情况下重新创建它。任何帮助将不胜感激。提前致谢!

die*_*per 10

这里你有另一个示例,没有使用 Listener/ScrollNotifications,只是调整大小:)。

结果:

在此输入图像描述

代码:

class SliverTest extends StatefulWidget {
  const SliverTest({super.key});

  @override
  State<SliverTest> createState() => _SliverTestState();
}

class _SliverTestState extends State<SliverTest> {
  @override
  Widget build(BuildContext context) {
    const circleSize = 50.0;
    const expandedHeight = 350.0;
    const headerColor = Colors.yellow;
    return MaterialApp(
      home: Scaffold(
        backgroundColor: headerColor,
        body: CustomScrollView(
          slivers: [
            SliverAppBar(
              expandedHeight: expandedHeight - circleSize / 2,
              floating: false,
              pinned: true,
              flexibleSpace: FlexibleSpaceBar(
                background: Container(
                  color: headerColor,
                  child: const Center(child: Text('Yellow')),
                ),
              ),
            ),
            SliverToBoxAdapter(
              child: Container(
                color: Colors.blueAccent,
                height: 200, //any size
                child: Stack(
                  children: [
                    Positioned(
                      top: 0,
                      left: 0,
                      right: 0,
                      height: circleSize / 2,
                      child: Container(
                        color: headerColor,
                      ),
                    ),
                    Align(
                      alignment: Alignment.topCenter,
                      child: Container(
                        width: circleSize,
                        height: circleSize,
                        decoration: const BoxDecoration(
                          shape: BoxShape.circle,
                          color: Colors.red,
                        ),
                        child: const Center(child: Text('Red')),
                      ),
                    ),
                  ],
                ),
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  return Container(
                    height: 50,
                    color: Colors.teal[100 * ((index % 8) + 1)],
                    child: Center(child: Text('Item #$index')),
                  );
                },
                childCount: 20,
              ),
            ),
          ],
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

更新

好的,根据您的最新评论,我必须更深入:),现在它正在按您上次要求的预期工作(但也更复杂)。

结果:

在此输入图像描述

代码:

class SliverTest extends StatefulWidget {
  const SliverTest({super.key});

  @override
  State<SliverTest> createState() => _SliverTestState();
}

class _SliverTestState extends State<SliverTest> {
  final _scrollController = ScrollController();
  final _layerLink = LayerLink();
  final _expandedHeight = 350.0;

  final _circleSize = 50.0;
  double _visiblePercent = 1.0;
  OverlayEntry? _overlayEntry;

  void _onListen() {
    final total = _scrollController.offset + kToolbarHeight + _circleSize / 2;
    final difference = total - _expandedHeight;
    _visiblePercent =
        ((_circleSize - difference).clamp(0.0, _circleSize).toDouble() /
            _circleSize);
    _overlayEntry?.markNeedsBuild();
  }

  @override
  void initState() {
    _scrollController.addListener(_onListen);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _showCircularItem();
    });
    super.initState();
  }

  @override
  void dispose() {
    _scrollController.removeListener(_onListen);
    _scrollController.dispose();
    _overlayEntry?.remove();
    super.dispose();
  }

  void _showCircularItem() {
    _overlayEntry = OverlayEntry(
      builder: (BuildContext context) {
        return Positioned(
          top: 0,
          left: 0,
          right: 0,
          child: CompositedTransformFollower(
            link: _layerLink,
            offset: Offset(0.0, -_circleSize / 2),
            child: Material(
              color: Colors.transparent,
              child: ClipRect(
                clipper: _MyClipper(
                  visiblePercent: _visiblePercent,
                ),
                child: Container(
                  width: _circleSize,
                  height: _circleSize,
                  decoration: const BoxDecoration(
                    shape: BoxShape.circle,
                    color: Colors.red,
                  ),
                  child: const Center(child: Text('Red')),
                ),
              ),
            ),
          ),
        );
      },
    );
    Overlay.of(context).insert(_overlayEntry!);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: CustomScrollView(
          controller: _scrollController,
          slivers: [
            SliverAppBar(
              expandedHeight: _expandedHeight,
              floating: false,
              pinned: true,
              flexibleSpace: FlexibleSpaceBar(
                background: Image.network(
                  'https://t4.ftcdn.net/jpg/05/49/86/39/360_F_549863991_6yPKI08MG7JiZX83tMHlhDtd6XLFAMce.jpg',
                  fit: BoxFit.cover,
                ),
              ),
            ),
            SliverToBoxAdapter(
              child: CompositedTransformTarget(
                link: _layerLink,
                child: Container(
                  color: Colors.greenAccent,
                  height: 200, //any size
                ),
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  return Container(
                    height: 50,
                    color: Colors.teal[100 * ((index % 8) + 1)],
                    child: Center(child: Text('Item #$index')),
                  );
                },
                childCount: 20,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class _MyClipper extends CustomClipper<Rect> {
  _MyClipper({
    required this.visiblePercent,
  });

  final double visiblePercent;

  @override
  Rect getClip(Size size) {
    return Rect.fromLTRB(
      0.0,
      size.height * (1.0 - visiblePercent),
      size.width,
      size.height,
    );
  }

  @override
  bool shouldReclip(_MyClipper oldClipper) {
    return visiblePercent != oldClipper.visiblePercent;
  }
}
Run Code Online (Sandbox Code Playgroud)


小智 -1

这是根据我的理解针对您的问题的解决方案。

class SliverTest extends StatefulWidget {
  const SliverTest({super.key});

  @override
  State<SliverTest> createState() => _SliverTestState();
}

class _SliverTestState extends State<SliverTest> {
  late ScrollController _scrollController;

  @override
  void initState() {
    _scrollController = ScrollController();
    super.initState();
  }

  @override
  void dispose() {
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: CustomScrollView(
          controller: _scrollController,
          shrinkWrap: true,
          slivers: [
            SliverAppBar.large(
              pinned: true,
              floating: false,
              stretch: true,
              automaticallyImplyLeading: false,
              expandedHeight: 350.0,
              backgroundColor: Colors.purple,
              title: Center(
                child: Container(
                  width: 50,
                  height: 50,
                  decoration: const BoxDecoration(
                    shape: BoxShape.circle,
                    color: Colors.red,
                  ),
                  child: const Center(child: Text('Red')),
                ),
              ),
              flexibleSpace: FlexibleSpaceBar(
                background: Container(
                  color: Colors.yellow,
                  child: const Center(child: Text('Yellow')),
                ),
              ),
            ),
            SliverToBoxAdapter(
              child: Container(
                // height: 100,
                color: Colors.blueAccent,
                child: Stack(
                  children: [
                    SingleChildScrollView(
                      child: Column(
                        children: [
                          for (int i = 0; i < 20; i++) ...[
                            Container(
                              height: 50,
                              color: Colors.teal[100 * ((i % 8) + 1)],
                              child: Center(child: Text('Item #$i')),
                            )
                          ],
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ),
            // SliverList(
            //   delegate: SliverChildBuilderDelegate(
            //     (BuildContext context, int index) {
            //       return Container(
            //         height: 50,
            //         color: Colors.teal[100 * ((index % 8) + 1)],
            //         child: Center(child: Text('Item #$index')),
            //       );
            //     },
            //     childCount: 20,
            //   ),
            // ),
          ],
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)