Sero
Sero

Reputation: 51

How can I have an animated SliverAppBar like this?

This is the SliverAppBar extended And this is the SliverAppBar collapsed

I want these animation between to be smooth, I tried using AnimatedSize, AnimatedOpacity and AnimatedPositioned but there were few errors. And I don't know how to use these with SliverAppBar. In other examples I saw people using LayoutBuilder but they didn't share the full code so I couldn't test it. I will share my code snippet but it has a really odd transition between the two states.

I want CircleAvatar to become smaller and change position I want Icon(Icons.arrow_back) and Text('Previous Page') to disappear. I also want my name and job to disappear. I want that three Containers to become smaller and change position.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dialog Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        body: CustomScrollView(
          slivers: [
            SliverAppBar(
              elevation: 0,
              pinned: true,
              expandedHeight: 190,
              collapsedHeight: kToolbarHeight + 8,
              flexibleSpace: FlexibleSpaceBar(
                collapseMode: CollapseMode.none,
                centerTitle: true,
                titlePadding: EdgeInsetsDirectional.only(
                  start: 20.0,
                  end: 20.0,
                  top: 12.0,
                  bottom: 12.0,
                ),
                title: SafeArea(
                  child: Column(
                    children: [
                      Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          CircleAvatar(
                            radius: 16,
                          ),
                          Row(
                            children: [
                              Container(
                                decoration: BoxDecoration(
                                  color: Colors.white10,
                                  borderRadius: BorderRadius.all(
                                    Radius.circular(12),
                                  ),
                                ),
                                padding: EdgeInsets.all(8.0),
                                child: Icon(Icons.person),
                              ),
                              SizedBox(
                                width: 8,
                              ),
                              Container(
                                decoration: BoxDecoration(
                                  color: Colors.white10,
                                  borderRadius: BorderRadius.all(
                                    Radius.circular(12),
                                  ),
                                ),
                                padding: EdgeInsets.all(8.0),
                                child: Icon(Icons.menu),
                              ),
                              SizedBox(
                                width: 8,
                              ),
                              Container(
                                decoration: BoxDecoration(
                                  color: Colors.white10,
                                  borderRadius: BorderRadius.all(
                                    Radius.circular(12),
                                  ),
                                ),
                                padding: EdgeInsets.all(8.0),
                                child: Icon(Icons.message),
                              ),
                            ],
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
                background: SafeArea(
                  child: Column(
                    children: [
                      Container(
                        alignment: Alignment.topLeft,
                        child: Row(
                          children: [
                            IconButton(
                              icon: Icon(Icons.arrow_back),
                              onPressed: () {},
                            ),
                            Text(
                              'PREVIOUS PAGE',
                              style: TextStyle(
                                fontSize: 12,
                                fontWeight: FontWeight.bold,
                              ),
                            ),
                          ],
                        ),
                      ),
                      Padding(
                        padding: const EdgeInsets.only(
                          left: 20,
                          top: 12,
                          bottom: 24,
                        ),
                        child: Row(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            CircleAvatar(
                              radius: 20,
                            ),
                            SizedBox(
                              width: 12,
                            ),
                            Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text(
                                  'Sertan Hakkı İmamoğlu',
                                  style: TextStyle(
                                    fontSize: 18,
                                    color: Colors.white,
                                    fontWeight: FontWeight.bold,
                                  ),
                                ),
                                Text(
                                  'Student',
                                  style: TextStyle(
                                    fontSize: 14,
                                    color: Colors.grey,
                                  ),
                                ),
                                SizedBox(
                                  height: 12,
                                ),
                                Row(
                                  children: [
                                    Container(
                                      decoration: BoxDecoration(
                                        color: Colors.white10,
                                        borderRadius: BorderRadius.all(
                                          Radius.circular(12),
                                        ),
                                      ),
                                      padding: EdgeInsets.symmetric(
                                        horizontal: 34,
                                        vertical: 12,
                                      ),
                                      child: Icon(Icons.menu),
                                    ),
                                    SizedBox(
                                      width: 4,
                                    ),
                                    Container(
                                      decoration: BoxDecoration(
                                        color: Colors.white10,
                                        borderRadius: BorderRadius.all(
                                          Radius.circular(12),
                                        ),
                                      ),
                                      padding: EdgeInsets.symmetric(
                                        horizontal: 34,
                                        vertical: 12,
                                      ),
                                      child: Icon(Icons.person),
                                    ),
                                    SizedBox(
                                      width: 4,
                                    ),
                                    Container(
                                      decoration: BoxDecoration(
                                        color: Colors.white10,
                                        borderRadius: BorderRadius.all(
                                          Radius.circular(12),
                                        ),
                                      ),
                                      padding: EdgeInsets.symmetric(
                                        horizontal: 34,
                                        vertical: 12,
                                      ),
                                      child: Icon(Icons.message),
                                    ),
                                  ],
                                )
                              ],
                            )
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
            SliverList(
              delegate: SliverChildBuilderDelegate(
                (BuildContext context, int index) {
                  return Container(
                    color: index.isOdd ? Colors.white : Colors.black12,
                    height: 100.0,
                    child: Center(
                      child: Text('$index', textScaleFactor: 5),
                    ),
                  );
                },
                childCount: 20,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Upvotes: 2

Views: 3013

Answers (1)

Guillaume Roux
Guillaume Roux

Reputation: 7328

Here's my take on your issue, as suggested by pskink I've used a SliverPersistentHeader and tried to make it responsive so when the size is reducing it has a bit of an animation.

Sample Code

class CustomPageHeader extends SliverPersistentHeaderDelegate {
  CustomPageHeader({
    required double collapsedHeight,
    required double expandedHeight,
  }) : minExtent = collapsedHeight, maxExtent = expandedHeight;

  @override
  final double minExtent;

  @override
  final double maxExtent;

  Widget _buildBtn(IconData icon, double scale) {
    double horizontal = 34.0 * scale;
    horizontal = horizontal < 8 ? 8.0 : horizontal;

    double vertical = 12.0 * scale;
    vertical = vertical < 8 ? 8.0 : vertical;

    return Container(
      decoration: const BoxDecoration(
        color: Colors.white10,
        borderRadius: BorderRadius.all(
          Radius.circular(12),
        ),
      ),
      padding: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical),
      child: Icon(icon),
    );
  }

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    final theme = Theme.of(context);
    final backgroundColor =
        theme.appBarTheme.backgroundColor ?? theme.colorScheme.primary;
    final scale = 1 - shrinkOffset / maxExtent;
    final isReduced = shrinkOffset >= maxExtent * 0.12;
    final wrappedBtns = Wrap(
      spacing: 8,
      children: [
        _buildBtn(Icons.menu, scale),
        _buildBtn(Icons.person, scale),
        _buildBtn(Icons.message, scale),
      ],
    );
    
    double avatarRadius = 20.0 * scale;
    avatarRadius = avatarRadius > 16 ? avatarRadius : 16.0;

    return Container(
      padding: const EdgeInsets.only(
        left: 20,
        top: 12,
        bottom: 12,
      ),
      color: backgroundColor,
      child: Column(
        children: [
          Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              CircleAvatar(radius: avatarRadius),
              !isReduced
                  ? const SizedBox(width: 12)
                  : Expanded(child: Container()),
              Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  if (!isReduced)
                    Text(
                      'Sertan Hakkı İmamoğlu',
                      style: TextStyle(
                        fontSize: 18 * scale,
                        color: Colors.white,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                  if (!isReduced)
                    Text(
                      'Student',
                      style: TextStyle(
                        fontSize: 14 * scale,
                        color: Colors.grey,
                      ),
                    ),
                ],
              ),
              if (isReduced) wrappedBtns,
            ],
          ),
          Flexible(
            child: Container(
              constraints: const BoxConstraints(maxHeight: 12),
            ),
          ),
          if (!isReduced) wrappedBtns,
        ],
      ),
    );
  }

  @override
  bool shouldRebuild(_) => true;
}

Use it in your sliver list like this:

SliverPersistentHeader(
  pinned: true,
  delegate: CustomPageHeader(
    collapsedHeight: kToolbarHeight + 8,
    expandedHeight: 190,
  ),
),

Try the full test code on DartPad

The code is perfectible but should help you enough so you can improve it and continue by yourself.

Upvotes: 5

Related Questions