Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flutter sliver persistent

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;
  }
}
like image 723
Mohammed Avatar asked Feb 21 '26 10:02

Mohammed


1 Answers

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. (UPDATE: improved fading as requested in the comment section)

class SliverHeaderDelegateComponent extends SliverPersistentHeaderDelegate {
  final double expandedHeight;

  const SliverHeaderDelegateComponent({required this.expandedHeight});

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    final deadline = (expandedHeight + minExtent);
    double percent = shrinkOffset > deadline ? 1 : shrinkOffset / deadline;
    final appBarSize = expandedHeight - shrinkOffset;
    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'))
                : 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: 1 - percent,
                // opacity: percent < 0.5 ? 1 : (1 - percent) * 2, // if you want to start fading when reach half way scroll
                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;
  }
}
like image 133
Ante Bule Avatar answered Feb 23 '26 02:02

Ante Bule