7

Here's what I want to build but I am able to achieve this Output

by the following code,

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NestedScrollView(
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled){
          return [
            SliverAppBar(
              expandedHeight: 120,
              floating: false,
              pinned: false,
              flexibleSpace: Container(
                padding: EdgeInsets.all(10),
                height: 160,
                width: double.infinity,
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.end,
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Align(
                        alignment: Alignment.topCenter,
                        child: Text(
                          'Hello World',
                        )),
                    Padding(
                      padding: EdgeInsets.only(top: 16),
                      child: Image.asset(
                        'assets/images/banner.png',
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ];
        },
        body: ListView.builder(),
      ),
    );
  }

I have tried SliverAppBar using flexible and expanded, but was not able to achieve.


Update - First element of list is going behind text field. I want to scroll only when the animation is completed

First element of list is going behind text field. I want to scroll only when the animation is completed

pratikkarad
  • 114
  • 2
  • 13
  • 1
    Personally, I think it's not possible with just SliversAppBar. You need to create your own custom app bar, then use Hero Animation (to move the text from left to center), AnimatedContainer (to reduce the height of container), AnimationController, FadeTransition, and listen to your Listview controller. It's a bit advanced. – fadhli-sulaimi Dec 13 '19 at 11:23
  • let me try this out, will get back to you after implementation. Thanks @FadhliS – pratikkarad Dec 13 '19 at 11:57
  • I will try to provide u a solution to this with all the widgets i mentioned. It's not easy for me either to achieve that effect u want. You can read this blog post i found to understand how animation works and hopefully help u. https://www.smashingmagazine.com/2019/10/animation-apps-flutter/ – fadhli-sulaimi Dec 13 '19 at 12:01

1 Answers1

18

updated answer,

   @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          TransitionAppBar(
            extent: 250,
            avatar: Text("Rancho"),
            title: Container(
              margin: EdgeInsets.symmetric(horizontal: 20.0, vertical: 0),
              decoration: BoxDecoration(
                  color: Colors.grey[200],
                  borderRadius: BorderRadius.all(Radius.circular(5.0))),
              child: Row(children: <Widget>[
                Padding(
                  padding: EdgeInsets.only(left: 20.0, right: 10.0),
                  child: Icon(Icons.search),
                ),
                Expanded(
                  child: TextFormField(
                    keyboardType: TextInputType.text,
                    textInputAction: TextInputAction.done,
                    cursorColor: Colors.black,
                    autofocus: false,
                    style: TextField_Style,
                    decoration: InputDecoration(
                        filled: true,
                        fillColor: Colors.transparent,
                        contentPadding:
                            EdgeInsets.symmetric(vertical: 10, horizontal: 15),
                        hintText: "Search",
                        border: InputBorder.none,
                        disabledBorder: OutlineInputBorder(
                          borderSide: new BorderSide(color: Colors.transparent),
                          borderRadius: new BorderRadius.circular(2),
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderSide: new BorderSide(color: Colors.transparent),
                          borderRadius: new BorderRadius.circular(2),
                        )),
                  ),
                )
              ]),
            ),
          ),
          SliverList(
              delegate: SliverChildBuilderDelegate((context, index) {
            return Container(
                color: Colors.blue,
                child: ListTile(
                  title: Text("${index}a"),
                ));
          }, childCount: 25))
        ],
      ),
    );
  } 

.

class TransitionAppBar extends StatelessWidget {
  final Widget avatar;
  final Widget title;
  final double extent;

  TransitionAppBar({this.avatar, this.title, this.extent = 250, Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SliverPersistentHeader(
      pinned: true,
      delegate: _TransitionAppBarDelegate(
        avatar: avatar,
        title: title,
        extent: extent > 200 ? extent : 200
      ),
    );
  }
}

class _TransitionAppBarDelegate extends SliverPersistentHeaderDelegate {
  final _avatarMarginTween = EdgeInsetsTween(
      begin: EdgeInsets.only(bottom: 70, left: 30),
      end: EdgeInsets.only(left: 0.0, top: 30.0));
  final _avatarAlignTween =
      AlignmentTween(begin: Alignment.bottomLeft, end: Alignment.topCenter);

  final Widget avatar;
  final Widget title;
  final double extent;

  _TransitionAppBarDelegate({this.avatar, this.title, this.extent = 250})
      : assert(avatar != null),
        assert(extent == null || extent >= 200),
        assert(title != null);

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    double tempVal = 34 * maxExtent / 100;
    final progress =  shrinkOffset > tempVal ? 1.0 : shrinkOffset / tempVal;
    print("Objechjkf === ${progress} ${shrinkOffset}");
    final avatarMargin = _avatarMarginTween.lerp(progress);
    final avatarAlign = _avatarAlignTween.lerp(progress);

    return Stack(
      children: <Widget>[
        AnimatedContainer(
          duration: Duration(milliseconds: 100),
          height: shrinkOffset * 2,
          constraints: BoxConstraints(maxHeight: minExtent),
          color: Colors.redAccent,
        ),
        Padding(
          padding: avatarMargin,
          child: Align(
            alignment: avatarAlign,
              child: avatar
          ),
        ),
        Padding(
          padding: EdgeInsets.only(bottom: 10),
          child: Align(
            alignment: Alignment.bottomCenter,
            child: title,
          ),
        )
      ],
    );
  }

  @override
  double get maxExtent => extent;

  @override
  double get minExtent => (maxExtent * 68) / 100;

  @override
  bool shouldRebuild(_TransitionAppBarDelegate oldDelegate) {
    return avatar != oldDelegate.avatar || title != oldDelegate.title;
  }
} 

enter image description here

  • 1
    **The element type '_TransitionAppBarDelegate' can't be assigned to the list type 'Widget'**. Getting this error while plugging in headerSliverBuilder – pratikkarad Dec 14 '19 at 09:04
  • 1
    Everything's working fine but if you see, the first element of list is going behind textfield before the completion of animation. I want to scroll the list only when the animation is completed. – pratikkarad Dec 15 '19 at 06:52
  • 2
    this is just demo code, customize code as per your need. – Nardeepsinh Vaghela Dec 16 '19 at 04:35
  • 1
    I managed everything, but how can I avoid the first element of list going behind textformfield before the completion of animation as animation depends on progress. Please help. @nardeepsinh-vaghela – pratikkarad Dec 18 '19 at 11:24
  • Pretty good, that was exactly what I'm looking for aha awesome, thank ! – Benjamin Jan 16 '22 at 16:31