nonybrighto
nonybrighto

Reputation: 9581

Show (slide in) or hide (slide out) flutter AppBar on screen tap

Please I am trying to create this effect where the AppBar slides out when the screen is tapped and slides in when it is tapped again.

sample image

I am able to create something similar in SliverAppBar by setting floating and snap to true. The difference is that the appBar shows when scrolling down and hides when screen is tapped or scrolled up.

Here is the sample code for the SliverAppBar:

 @override
  Widget build(BuildContext context) {

    return Scaffold(
          body: CustomScrollView(
            controller: _ctrlr,

                slivers: <Widget>[
                  SliverAppBar(
                    floating: true,
                    snap: true,

                  ),
                  SliverList(
                    delegate: SliverChildListDelegate([
                      Text('1', style: TextStyle(fontSize: 160.0),),
                      Text('2', style: TextStyle(fontSize: 160.0),),
                      Text('3', style: TextStyle(fontSize: 160.0),),
                      Text('4', style: TextStyle(fontSize: 160.0),),
                    ]),
                  )
                ],
        ),
    );
  }  

How can I be able to achieve this? I also considered placing the AppBar in a Stack but i don't think that is the best approach. Your help will be greatly appreciated!

Upvotes: 4

Views: 5811

Answers (4)

Dexter404
Dexter404

Reputation: 35

I came across a better approach suggested by CopsOnRoad here: Flutter - How can I dynamically show or hide App Bars on pages

Just re-sharing for others.

We can abstract the animation part in another widget like:

class SlidingAppBar extends PreferredSize {
  SlidingAppBar({
    @required this.child,
    @required this.controller,
    @required this.visible,
  });

  @override
  final PreferredSizeWidget child;

  @override
  Size get preferredSize => child.preferredSize;

  final AnimationController controller;
  final bool visible;

  @override
  Widget build(BuildContext context) {
    visible ? controller.reverse() : controller.forward();
    return SlideTransition(
      position: Tween<Offset>(begin: Offset.zero, end: Offset(0, -1)).animate(
        CurvedAnimation(parent: controller, curve: Curves.fastOutSlowIn),
      ),
      child: child,
    );
  }
}

Now this SlidingAppBar widget can be used with Scaffold's appBar field instead of using Stack widget like:

class _MyPageState extends State<MyPage> with SingleTickerProviderStateMixin {
  bool _visible = true;
  AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 400),
    );
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // extendBodyBehindAppBar: !_visible, // Uses entire screen after hiding AppBar
      appBar: SlidingAppBar(
        controller: _controller,
        visible: _visible,
        child: AppBar(title: Text('AppBar')),
      ),
      body: GestureDetector(
        onTap: () => setState(() => _visible = !_visible),
        child: Container(
          height: double.infinity,
          child: Text('App content...'),
        )
      )
    );
  }
}

Upvotes: 0

januw a
januw a

Reputation: 2248

This is my answer:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  bool show = true;
  @override
  Widget build(BuildContext context) {
    var appbar = AppBar();
    var maxHeight = appbar.preferredSize.height;
    return SafeArea(
      child: Scaffold(
        body: Stack(
          children: <Widget>[
            Positioned.fill(
                child: GestureDetector(
                    onTap: () {
                      setState(() {
                        show = !show;
                      });
                    },
                    child: Container(
                        color: Colors.black,
                        child: Center(child: FlutterLogo())))),
            AnimatedAlign(
              duration: kThemeAnimationDuration,
              alignment: Alignment(0, show ? -1 : -2),
              child: ConstrainedBox(
                constraints: BoxConstraints(maxHeight: maxHeight),
                child: FlexibleSpaceBar.createSettings(
                  currentExtent: maxHeight,
                  child: appbar,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Upvotes: 0

user12080928
user12080928

Reputation:

Just replace the SlideTransition in Bob H.'s solution with an AnimatedBuilder and a Transform.translate widget:

                          animation: offsetAnimation,
                          builder: (context, child) {
                            return Transform.translate(
                                offset: offsetAnimation.value,
                                child: Container( ....```

Upvotes: 2

b0bh00d
b0bh00d

Reputation: 131

I came upon a similar need, and discovered your question. Since there were no answers, I took it upon myself to try and solve the problem. I know you asked this 6 months ago, but I'm putting in an (nearly complete) answer in case anybody else happens upon it.

(I apologize if my approach is less than elegant, but as of this writing, I've only been using Flutter for about a week. :)

import 'package:flutter/material.dart';
import 'package:transparent_image/transparent_image.dart';

class FramePage extends StatefulWidget {
  final String title;
  final String imageUrl;

  FramePage({Key key, this.title, this.imageUrl}) : super(key: key);

  @override
  _FramePageState createState() => _FramePageState();
}

class _FramePageState extends State<FramePage> with SingleTickerProviderStateMixin {
  AnimationController _controller;
  bool _appBarVisible;

  @override
  void initState() {
    super.initState();

    _appBarVisible = true;
    _controller = AnimationController(
      duration: const Duration(milliseconds: 700),
      value: 1.0,
      vsync: this,
    );
  }

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

  void _toggleAppBarVisibility() {
    _appBarVisible = !_appBarVisible;
    _appBarVisible ? _controller.forward() : _controller.reverse();
  }

  Widget get _imageWidget {
    return Center(
      child: GestureDetector(
      onTap: () => setState(() { _toggleAppBarVisibility(); } ),
      child: Container(
          foregroundDecoration: new BoxDecoration(color: Color.fromRGBO(155, 85, 250, 0.0)),
          child: FadeInImage.memoryNetwork(
            placeholder: kTransparentImage,
            image: widget.imageUrl,
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context)
  {
    Animation<Offset> offsetAnimation = new Tween<Offset>(
      begin: Offset(0.0, -70),
      end: Offset(0.0, 0.0),
    ).animate(_controller);

    return Scaffold(
      body: Stack(
        children: <Widget>[
          _imageWidget,
          SlideTransition(
            position: offsetAnimation,
            child: Container(
              height: 75,
              child: AppBar(
                title: Text(widget.title),
              ),
            ),
          ),
        ],
      )
    );
  }
}

Essentially, the AppBar is removed as a direct part of the Scaffold, and instead is added to the Stack, where it can actually be animated. In order to make sure the image is visible behind it, it is placed into a Container so it's height can be controlled (otherwise, you would not be able to see the image).

In my code above, tapping on the image makes the AppBar retract, and tapping again makes it re-appear. For some reason, though, I haven't been able to actually get it to smoothly animate forward and back, but the effect is there.

In practice, it looks like this:

Animating the AppBar visibility

If somebody figures out (before I do) what I've missed to make it animate smoothly, please feel free to help out.

Upvotes: 4

Related Questions