Home > Software engineering >  Flutter sliver persistent
Flutter sliver persistent

Time:01-16

I have the code below, which I shortened as much as possible to make it easier to deal with. I want to scroll down to show the default appBar, not the background. I did some solutions, but it didn't work. Switch between them with a smooth motion.

I want to use the same existing code because I built on it.

I have attached an illustration of the problem

enter image description here

The main code:

import 'package:flutter/material.dart';

import 'home_page.dart';

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

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

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

HomePage code:

import 'package:flutter/material.dart';

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          const SliverPersistentHeader(pinned: true, delegate: SliverHeaderDelegateComponent(expandedHeight: 300)),
          SliverList(
            delegate: SliverChildListDelegate(
              [
                Container(
                  height: 1000,
                  color: Colors.blue.withOpacity(0.5),
                  child: const Center(child: Text('Body')),
                )
              ],
            ),
          ),
        ],
      ),
    );
  }
}

The SliverHeaderDelegateComponent code :

class SliverHeaderDelegateComponent extends SliverPersistentHeaderDelegate {
  final double expandedHeight;

  const SliverHeaderDelegateComponent({required this.expandedHeight});

  @override
  Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
    final appBarSize = expandedHeight - shrinkOffset;
    final proportion = 2 - (expandedHeight / appBarSize);
    final percent = proportion < 0 || proportion > 1 ? 0.0 : proportion;
    return StatefulBuilder(
      builder: (BuildContext context, StateSetter setState) => SizedBox(
        height: expandedHeight   expandedHeight / 2,
        child: Stack(
          clipBehavior: Clip.none,
          children: [
            Container(
              height: 500,
              decoration: const BoxDecoration(
                color: Colors.black,
                image: DecorationImage(
                  image: NetworkImage(
                      'https://www.digitalartsonline.co.uk/cmsdata/slideshow/3662115/baby-driver-rory-hi-res.jpg'),
                  fit: BoxFit.cover,
                ),
              ),
            ),
            PositionedDirectional(
              start: 0.0,
              end: 0.0,
              top: appBarSize > 0 ? appBarSize : 0,
              bottom: -100,
              child: Opacity(
                opacity: percent,
                child: Padding(
                  padding: EdgeInsets.symmetric(horizontal: 30 * percent),
                  child: const Card(
                    elevation: 20.0,
                    child: Center(
                      child: Text("Widget"),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  double get maxExtent => expandedHeight   expandedHeight / 2;

  @override
  double get minExtent => kToolbarHeight;

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
    return true;
  }
}

CodePudding user response:

Here is the solution using SliverHeaderDelegateComponent as you requested. In this example, the AppBar is shown when collapsed, but you can uncomment the commented part if you want to show it on expand.

class SliverHeaderDelegateComponent extends SliverPersistentHeaderDelegate {
  final double expandedHeight;

  const SliverHeaderDelegateComponent({required this.expandedHeight});

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    final appBarSize = expandedHeight - shrinkOffset;
    final proportion = 2 - (expandedHeight / appBarSize);
    final percent = proportion < 0 || proportion > 1 ? 0.0 : proportion;
    return StatefulBuilder(
      builder: (BuildContext context, StateSetter setState) => SizedBox(
        height: expandedHeight   expandedHeight / 2,
        child: Stack(
          clipBehavior: Clip.none,
          children: [
            // shrinkOffset == 0 // if you want to show it on expand
            shrinkOffset > expandedHeight   minExtent // show it on collapse
                ? AppBar(title: Text('App Bar')) // the component you want to show
                : Container(
                    height: 500,
                    decoration: const BoxDecoration(
                      color: Colors.black,
                      image: DecorationImage(
                        image: NetworkImage(
                            'https://www.digitalartsonline.co.uk/cmsdata/slideshow/3662115/baby-driver-rory-hi-res.jpg'),
                        fit: BoxFit.cover,
                      ),
                    ),
                  ),
            PositionedDirectional(
              start: 0.0,
              end: 0.0,
              top: appBarSize > 0 ? appBarSize : 0,
              bottom: -100,
              child: Opacity(
                opacity: percent,
                child: Padding(
                  padding: EdgeInsets.symmetric(horizontal: 30 * percent),
                  child: const Card(
                    elevation: 20.0,
                    child: Center(
                      child: Text("Widget"),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  @override
  double get maxExtent => expandedHeight   expandedHeight / 2;

  @override
  double get minExtent => kToolbarHeight;

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
    return true;
  }
}
  • Related