Flutter: Best way to change a widget opacity and color on scroll

11,490

i think the best approach Will be using AnimatedBuilder and you will see that first container in body will not changed its color because widget state have not changed and the result :

enter image description here

code:

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

class ProductDetails extends StatefulWidget {
  @override
  _ProductDetailsState createState() => _ProductDetailsState();
}

class _ProductDetailsState extends State<ProductDetails>
    with TickerProviderStateMixin {
  AnimationController _ColorAnimationController;
  AnimationController _TextAnimationController;
  Animation _colorTween, _iconColorTween;
  Animation<Offset> _transTween;

  @override
  void initState() {
    _ColorAnimationController =
        AnimationController(vsync: this, duration: Duration(seconds: 0));
    _colorTween = ColorTween(begin: Colors.transparent, end: Color(0xFFee4c4f))
        .animate(_ColorAnimationController);
    _iconColorTween = ColorTween(begin: Colors.grey, end: Colors.white)
        .animate(_ColorAnimationController);


    _TextAnimationController =
        AnimationController(vsync: this, duration: Duration(seconds: 0));

    _transTween = Tween(begin: Offset(-10, 40), end: Offset(-10, 0))
        .animate(_TextAnimationController);

    super.initState();
  }

  bool _scrollListener(ScrollNotification scrollInfo) {
    if (scrollInfo.metrics.axis == Axis.vertical) {
      _ColorAnimationController.animateTo(scrollInfo.metrics.pixels / 350);

      _TextAnimationController.animateTo(
          (scrollInfo.metrics.pixels - 350) / 50);
      return true;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color(0xFFEEEEEE),
      body: NotificationListener<ScrollNotification>(
        onNotification: _scrollListener,
        child: Container(
          height: double.infinity,
          child: Stack(
            children: <Widget>[
              SingleChildScrollView(
                child: Column(
                  children: <Widget>[
                    Container(
                      height: 150,
                      color:
                          Color((Random().nextDouble() * 0xFFFFFF).toInt() << 0)
                              .withOpacity(1),
                      width: 250,
                    ),
                    Container(
                      height: 150,
                      color: Colors.pink,
                      width: 250,
                    ),
                    Container(
                      height: 150,
                      color: Colors.deepOrange,
                      width: 250,
                    ),
                    Container(
                      height: 150,
                      color: Colors.red,
                      width: 250,
                    ),
                    Container(
                      height: 150,
                      color: Colors.white70,
                      width: 250,
                    ),
                  ],
                ),
              ),
              Container(
                height: 80,
                child: AnimatedBuilder(
                  animation: _ColorAnimationController,
                  builder: (context, child) => AppBar(
                    backgroundColor: _colorTween.value,
                    elevation: 0,
                    titleSpacing: 0.0,
                    title: Transform.translate(
                      offset: _transTween.value,
                      child: Text(
                        "اسم کالا اینجا",
                        style: TextStyle(
                            color: Colors.white,
                            fontWeight: FontWeight.bold,
                            fontSize: 16),
                      ),
                    ),
                    iconTheme: IconThemeData(
                      color: _iconColorTween.value,
                    ),
                    actions: <Widget>[
                      IconButton(
                        icon: Icon(
                          Icons.local_grocery_store,
                        ),
                        onPressed: () {
//                          Navigator.of(context).push(TutorialOverlay());
                        },
                      ),
                      IconButton(
                        icon: Icon(
                          Icons.more_vert,
                        ),
                        onPressed: () {},
                      ),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
Share:
11,490
woshitom
Author by

woshitom

Updated on June 05, 2022

Comments

  • woshitom
    woshitom almost 2 years

    My goal is to change the color and the opacity of the appbar when user scrolls down.

    My logic is:

    • scroll offset = 0 : appbar is red with opacity = 1
    • 0 < scroll offset < 40 : appbar is blue with opacity = 0.4
    • 40 <= scroll offset : appbar is blue with opacity proportional to scroll offset

    I came up with the following code:

    import 'package:flutter/material.dart';
    import 'package:gradient_app_bar/gradient_app_bar.dart';
    
    void main() => runApp(MyApp());
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: MyHomePage(title: 'Flutter Demo Home Page'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      MyHomePage({Key key, this.title}) : super(key: key);
    
      final String title;
    
      @override
      _MyHomePageState createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      var _gradientColor1 = Colors.red[400];
      var _gradientColor2 = Colors.red[800];
      ScrollController _scrollViewController;
    
      void changeColor(){
        if((_scrollViewController.offset == 0) && (_gradientColor1 != Colors.red[400])){
          setState(() {
            _gradientColor1 = Colors.red[400];
            _gradientColor2 = Colors.red[800];
          });
        }else if((_scrollViewController.offset <= 40) && (_gradientColor1 != Color.fromRGBO(66,165,245 ,0.4))){
          setState(() {
            _gradientColor1 = Color.fromRGBO(66,165,245 ,0.4);
            _gradientColor2 = Color.fromRGBO(21,101,192 ,0.4);
          });
        }else if((_scrollViewController.offset <= 100) && (_scrollViewController.offset > 40)){
          var opacity = _scrollViewController.offset/100;
          setState(() {
            _gradientColor1 = Color.fromRGBO(66,165,245 ,opacity);
            _gradientColor2 = Color.fromRGBO(21,101,192 ,opacity);
          });
        }
      }
    
      @override
      void initState() {
        _scrollViewController = ScrollController(initialScrollOffset: 0.0);
        _scrollViewController.addListener(changeColor);
      }
    
      @override
      Widget build(BuildContext context) {
    
        return Scaffold(
          appBar: GradientAppBar(
            backgroundColorStart: _gradientColor1,
            backgroundColorEnd: _gradientColor2,
            elevation: 0,
          ),
          body: SingleChildScrollView(
            controller: _scrollViewController,
            child: Column(
              children: <Widget>[
                Container(color: Colors.red, height: 400,),
                Container(color: Colors.purple, height: 400,),
              ],
            ),
          ),
        );
      }
    }
    

    It works as expected but it becomes laggy with a more complicated UI. In my example I'm using GradientAppbar: https://github.com/joostlek/GradientAppBar

  • JediBurrell
    JediBurrell almost 3 years
    calling setState every time the scroll updates is terrible for performance.
  • Iván Yoed
    Iván Yoed almost 3 years
    You have a good point there. Whoever wants to implement this should make sure setState only gets gets called when necessary. Suggestions are very welcomed.
  • Nithin Sai
    Nithin Sai over 2 years
    Is this the best method keeping performance in mind?
  • Kamil Svoboda
    Kamil Svoboda over 2 years
    It is possible to add animated AppBar directly to the Staffold now, using extendBodyBehindAppBar: true, (from Flutter stable 1.12+)