flutterflutter-animationflutter-appbarflutter-container

Is there a correct way to switch between two widgets (app bars in my case) by swiping up and down?


I'm currently working on a page in my Flutter app and facing challenges implementing a specific logic. When the app launches, I want the page to initially display the 'appbardown' widget (the larger one) and remain in that state until a user decides to swipe upwards or scroll through the list of 'Ones' within one of the tabs.

Currently, when I attempt to swipe upwards, the widget shifts to the 'appbarup' widget, but the transition is neither smooth nor efficient. Additionally, when I try to scroll the list of 'Ones,' I want the 'appbardown' widget to smoothly transition to the 'appbarup' widget before allowing me to scroll the list.

Conversely, when I swipe downward, the widget should shift from 'appbarup' to 'appbardown.' Similarly, when I scroll downwards in the list of 'Ones,' the appbar should shift accordingly.

If you have any insights or a better solution for achieving this functionality, I would greatly appreciate your assistance.

import 'package:flutter/material.dart';

import 'widgets/quick_links.dart';

class TempScreen extends StatefulWidget {
  const TempScreen({super.key});

  @override
  State<TempScreen> createState() => _TempScreenState();
}

class _TempScreenState extends State<TempScreen> {
  final controller = PageController(initialPage: 1);
  bool isSecondOrder = false;

  @override
  Widget build(BuildContext context) {
    Size size = MediaQuery.of(context).size;
    return Scaffold(
      body: GestureDetector(
        onVerticalDragEnd: (details) {
          if (details.primaryVelocity! > 0) {
            // Swiped downwards
            setState(() {
              isSecondOrder = false;
            });
          } else if (details.primaryVelocity! < 0) {
            //Swiped Upwards
            setState(() {
              isSecondOrder = true;
            });
          }
        },
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            AnimatedSwitcher(
              switchInCurve: Curves.easeInOut, // Use your preferred curve
              switchOutCurve: Curves.easeInOut, // Use your preferred curve
              transitionBuilder: (child, animation) {
                return Container(
                  color: Theme.of(context).primaryColor,
                  child: FadeTransition(
                    opacity: animation,
                    child: child,
                  ),
                );
              },
              duration: const Duration(milliseconds: 1),
              child: isSecondOrder
                  ? AppBarUpt(size: size)
                  : AppBarDownt(size: size),
            ),
            Padding(
              padding: EdgeInsets.only(
                  left: size.width * 0.05, top: size.height * 0.035),
              child: const Text(
                'Quick Links',
                style: TextStyle(
                  fontSize: 20.0,
                  color: Colors.black,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
            SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Padding(
                padding: EdgeInsets.only(
                  left: size.width * 0.05,
                ),
                child: const Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text('A'),
                    Text('B'),
                    Text('C'),
                    Text('D'),
                    Text('E'),
                  ],
                ),
              ),
            ),
            Expanded(
              child: Padding(
                padding: EdgeInsets.only(
                  top: 20,
                  left: size.width * 0.04,
                  right: size.width * 0.04,
                ),
                child: DefaultTabController(
                  length: 3, // Number of tabs
                  child: Column(
                    children: [
                      Padding(
                        padding: EdgeInsets.only(
                          // top: 20,
                          left: size.width * 0.04,
                          right: size.width * 0.04,
                        ),
                        child: const TabBar(
                          indicatorWeight:
                              4.0, // Adjust indicator weight as needed
                          indicatorColor: Colors.black,
                          // labelPadding: EdgeInsets.symmetric(horizontal: 16.0),
                          labelStyle: TextStyle(
                            fontSize: 18.0,
                            fontWeight: FontWeight.w700,
                          ),
                          tabs: [
                            Tab(text: '1'),
                            Tab(text: '2'),
                            Tab(text: '3'),
                          ],
                          unselectedLabelColor:
                              Color.fromARGB(255, 186, 186, 186),
                        ),
                      ),
                      const Expanded(
                        child: TabBarView(
                          children: [
                            // Your Surah page content
                            One(),
                            Two(),
                            Three(),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const SingleChildScrollView(
      padding: EdgeInsets.only(
        top: 10,
      ),
      child: Column(
        children: [
          Text(
            'one',
            style: TextStyle(
              fontSize: 50,
            ),
          ),
          Text(
            'one',
            style: TextStyle(
              fontSize: 50,
            ),
          ),
          Text(
            'one',
            style: TextStyle(
              fontSize: 50,
            ),
          ),
          Text(
            'one',
            style: TextStyle(
              fontSize: 50,
            ),
          ),
          Text(
            'one',
            style: TextStyle(
              fontSize: 50,
            ),
          ),
          Text(
            'one',
            style: TextStyle(
              fontSize: 50,
            ),
          ),
          Text(
            'one',
            style: TextStyle(
              fontSize: 50,
            ),
          ),
          Text(
            'one',
            style: TextStyle(
              fontSize: 50,
            ),
          ),
        ],
      ),
    );
  }
}
class Two extends StatelessWidget {
  const Two({super.key});

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text('Two'),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return const Center(
      child: Text('Three'),
    );
  }
}

class AppBarDownt extends StatelessWidget {
  const AppBarDownt({
    super.key,
    required this.size,
  });

  final Size size;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: size.height * 0.29,
      decoration: BoxDecoration(
        color: Theme.of(context).primaryColor,
        borderRadius: const BorderRadius.only(
          bottomLeft: Radius.circular(20),
          bottomRight: Radius.circular(20),
        ),
      ),
    );
  }
}

class AppBarUpt extends StatelessWidget {
  const AppBarUpt({
    super.key,
    required this.size,
  });

  final Size size;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      height: size.height * 0.15,
      decoration: BoxDecoration(
        color: Theme.of(context).primaryColor,
        borderRadius: const BorderRadius.only(
          bottomLeft: Radius.circular(20),
          bottomRight: Radius.circular(20),
        ),
      ),
    );
  }
}

Solution

  • Result

    enter image description here

    Explanation

    Since you are dealing with an AppBar, you are probably looking for a NestedScrollView /SliverAppBar.

    To detect whether you are currently scrolling you can use a NotificationListener.

    Code

    import 'package:flutter/material.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatefulWidget {
      @override
      State<MyApp> createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      bool isScrolling = false;
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Material App',
          home: Scaffold(
            body: Scaffold(
              body: NestedScrollView(
                headerSliverBuilder: (context, _) => [
                  isScrolling
                      ? SliverAppBar(
                          pinned: true,
                          flexibleSpace: Container(
                            color: Colors.green,
                            child: FlexibleSpaceBar(
                              title: Text(
                                'YES scrolling',
                                style: TextStyle(color: Colors.black),
                              ),
                            ),
                          ),
                        )
                      : SliverAppBar(
                          pinned: true,
                          flexibleSpace: Container(
                            color: Colors.red,
                            child: FlexibleSpaceBar(
                              title: Text(
                                'NOT scrolling',
                                style: TextStyle(color: Colors.black),
                              ),
                            ),
                          ),
                        ),
                ],
                body: NotificationListener<ScrollNotification>(
                  onNotification: (scrollNotification) {
                    // check if the user is scrolling
                    if (scrollNotification is ScrollStartNotification) {
                      setState(() {
                        isScrolling = true;
                      });
                    } else if (scrollNotification is ScrollEndNotification) {
                      setState(() {
                        isScrolling = false;
                      });
                    }
                    return true;
                  },
                  child: ListView.builder(
                    itemCount: 100,
                    itemBuilder: (context, index) => ListTile(
                      title: Text('Item $index'),
                    ),
                  ),
                ),
              ),
            ),
          ),
        );
      }
    }
    
    

    If you want animations, you can use AnimtedSwitcher. For example:

     flexibleSpace: AnimatedSwitcher(
                    duration: Duration(seconds: 1),
                    child: isScrolling
                        ? Container(
                            key: ValueKey('AppBar1'),
                            color: Colors.green,
                            child: FlexibleSpaceBar(
                              title: Text('YES scrolling',
                                  style: TextStyle(color: Colors.black)),
                            ),
                          )
                        : Container(
                            key: ValueKey('AppBar2'),
                            color: Colors.red,
                            child: FlexibleSpaceBar(
                              title: Text('NOT scrolling',
                                  style: TextStyle(color: Colors.black)),
                            ),
                          ),
                  ),