如何在颤动中添加主题切换动画? [英] how to add animation for theme switching in flutter?

查看:16
本文介绍了如何在颤动中添加主题切换动画?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在 flutter 中添加动画以将主题从浅色切换到深色,反之亦然,就像 telegram 一样:

更新!我已经用简单的 api 将我们的代码转换为 一个包.

import 'package:equatable/equatable.dart';导入包:颤振/material.dart";无效的主要()=>运行应用程序(我的应用程序());MyApp 类扩展 StatelessWidget {@覆盖小部件构建(BuildContext 上下文){返回品牌主题(孩子:建造者(建造者:(上下文){返回材料应用程序(标题:颤振演示",主题:BrandTheme.of(context).themeData,主页:我的主页(),);}),);}}GlobalKey 交换器GlobalKey = GlobalKey();类 MyHomePage 扩展 StatefulWidget {MyHomePage({Key key}) : super(key: key);@覆盖_MyHomePageState createState() =>_MyHomePageState();}类 _MyHomePageState 扩展状态<MyHomePage>与 SingleTickerProviderStateMixin {动画控制器_控制器;@覆盖无效初始化状态(){_控制器=动画控制器(持续时间:常量持续时间(毫秒:300),垂直同步:这个,);_controller.forward();super.initState();}@覆盖无效处置(){_controller.dispose();super.dispose();}int _counter = 0;品牌主题模型老主题;偏移切换器Offset;无效_incrementCounter(){设置状态((){_计数器++;});}_getPage(brandTheme, {isFirst = false}) {返回脚手架(背景颜色:brandTheme.color2,应用栏:应用栏(背景颜色:brandTheme.color1,标题:文字('Flutter 演示主页',样式:TextStyle(颜色:brandTheme.textColor2),),),身体:中心(孩子:列(mainAxisAlignment: MainAxisAlignment.spaceAround,孩子们:<小部件>[文本('你已经多次按下按钮:',样式:文本样式(颜色:brandTheme.textColor1,),),文本('$_counter',样式:TextStyle(颜色:brandTheme.textColor1,fontSize:200),),转变(关键:是第一?开关全局键:空,onChanged: (needDark) {oldTheme = 品牌主题;BrandTheme.instanceOf(context).changeTheme(需要黑暗?BrandThemeKey.dark : BrandThemeKey.light,);},值:BrandTheme.of(context).brightness == Brightness.dark,)],),),浮动操作按钮:浮动操作按钮(onPressed:_incrementCounter,工具提示:'增量',孩子:图标(Icons.add,),),);}@覆盖无效 didUpdateWidget(小部件 oldWidget){var 主题 = BrandTheme.of(context);如果(主题!= oldTheme){_getSwitcherCodinates();_controller.reset();_controller.forward().then((_) {oldTheme = 主题;},);}super.didUpdateWidget(oldWidget);}无效 _getSwitcherCodinates() {RenderBox renderObject = switherGlobalKey.currentContext.findRenderObject();switcherOffset = renderObject.localToGlobal(Offset.zero);}@覆盖小部件构建(BuildContext 上下文){var brandTheme = BrandTheme.of(context);if (oldTheme == null) {return _getPage(brandTheme, isFirst: true);}返回堆栈(孩子们:<小部件>[如果(旧主题!= null)_getPage(旧主题),动画生成器(动画:_控制器,孩子:_getPage(brandTheme,isFirst:true),建设者:(_,孩子){返回剪辑路径(剪裁器:MyClipper(sizeRate: _controller.value,偏移量:switcherOffset.translate(30, 15),),孩子:孩子,);},),],);}}MyClipper 类扩展了 CustomClipper<路径>;{MyClipper({this.sizeRate, this.offset});最终双 sizeRate;最终偏移量;@覆盖路径 getClip(Size size) {变量路径 = 路径()..addOval(Rect.fromCircle(center: offset, radius: size.height * sizeRate),);返回路径;}@覆盖bool shouldReclip(CustomClipper<Path> oldClipper) =>真的;}类 BrandTheme 扩展 StatefulWidget {最后的小部件子;品牌主题({钥匙钥匙,@required this.child,}) : 超级(键:键);@覆盖BrandThemeState createState() =>品牌主题状态();静态 BrandThemeModel of(BuildContext context) {最终继承=(context.dependOnInheritedWidgetOfExactType<_InheritedBrandTheme>());返回继承的.data.brandTheme;}静态 BrandThemeState instanceOf(BuildContext context) {最终继承=(context.dependOnInheritedWidgetOfExactType<_InheritedBrandTheme>());返回继承的.data;}}类 BrandThemeState 扩展了状态<BrandTheme>{BrandThemeModel _brandTheme;BrandThemeModel 获取品牌主题 =>_品牌主题;@覆盖无效初始化状态(){最终的 isPlatformDark =WidgetsBinding.instance.window.platformBrightness == Brightness.dark;最终主题键 = isPlatformDark ?BrandThemeKey.dark : BrandThemeKey.light;_brandTheme = BrandThemes.getThemeFromKey(themeKey);super.initState();}无效changeTheme(BrandThemeKey主题键){设置状态((){_brandTheme = BrandThemes.getThemeFromKey(themeKey);});}@覆盖小部件构建(BuildContext 上下文){返回_继承品牌主题(数据:这个,孩子:widget.child,);}}类 _InheritedBrandTheme 扩展 InheritedWidget {最终的 BrandThemeState 数据;_继承品牌主题({这个数据,钥匙钥匙,@required 小部件子项,}) : super(key: key, child: child);@覆盖bool updateShouldNotify(_InheritdBrandTheme oldWidget) {返回真;}}主题数据 defaultThemeData = ThemeData(浮动操作按钮主题:浮动操作按钮主题数据(形状:圆角矩形边框(),),);类 BrandThemeModel 扩展 Equatable {最终颜色 color1;最终颜色 color2;最终颜色 textColor1;最终颜色 textColor2;最终 ThemeData 主题数据;最终亮度亮度;品牌主题模型({@required this.color1,@需要这个.color2,@required this.textColor1,@required this.textColor2,@required this.brightness,}) : themeData = defaultThemeData.copyWith(brightness: 亮度);@覆盖列出<对象>获取道具 =>[颜色1,颜色2,文本颜色1,文本颜色2,主题数据,亮度,];}枚举 BrandThemeKey { 浅色,深色 }类品牌主题{静态 BrandThemeModel getThemeFromKey(BrandThemeKey themeKey) {开关(主题键){案例 BrandThemeKey.light:返回 lightBrand 主题;案例 BrandThemeKey.dark:返回黑暗品牌主题;默认:返回 lightBrand 主题;}}}BrandThemeModel lightBrandTheme = BrandThemeModel(亮度:Brightness.light,color1:颜色.蓝色,color2:颜色.白色,textColor1:颜色.黑色,textColor2:颜色.白色,);BrandThemeModel darkBrandTheme = BrandThemeModel(亮度:Brightness.dark,color1:颜色.红色,color2:颜色.黑色,textColor1:颜色.蓝色,textColor2:颜色.黄色,);类 ThemeRoute 扩展 PageRouteBuilder {ThemeRoute(this.widget): 极好的(页面构建器:(语境,动画片,二次动画,) =>小部件,转换构建器:转换构建器,);最终的 Widget 小部件;}小部件转换生成器(BuildContext上下文,动画<双重>动画片,动画<双重>二次动画,小部件子,) {var _animation = Tween<double>(开始:0,结束:100,).animate(动画);返回幻灯片转换(位置:补间<偏移>(开始:常量偏移(0, 1),结束:偏移量.零,).animate(动画),孩子:容器(孩子:孩子,),);}

I want to add animation for switching theme from light to dark or vice versa in flutter like telegram do :

telegram's switch animation

telegram's switch animation

source

can't see any way to do it in flutter, is it possible in flutter?

thx for any answer

解决方案

It’s not hard, but you need to do several things.

  1. You need to create your own theme styles. I’ve used inherited widget to do it. (If you change ThemeData widget it will animate the change, and we don’t need it, that’s why I’m saving Colors in another class)
  2. Find the button (or in my case switcher) coordinates.
  3. Run animation.

update! I've converted our code to a package with simple api.

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BrandTheme(
      child: Builder(builder: (context) {
        return MaterialApp(
          title: 'Flutter Demo',
          theme: BrandTheme.of(context).themeData,
          home: MyHomePage(),
        );
      }),
    );
  }
}

GlobalKey switherGlobalKey = GlobalKey();

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

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

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;

  @override
  void initState() {
    _controller = AnimationController(
      duration: const Duration(milliseconds: 300),
      vsync: this,
    );

    _controller.forward();
    super.initState();
  }

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

    super.dispose();
  }

  int _counter = 0;
  BrandThemeModel oldTheme;
  Offset switcherOffset;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  _getPage(brandTheme, {isFirst = false}) {
    return Scaffold(
      backgroundColor: brandTheme.color2,
      appBar: AppBar(
        backgroundColor: brandTheme.color1,
        title: Text(
          'Flutter Demo Home Page',
          style: TextStyle(color: brandTheme.textColor2),
        ),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
              style: TextStyle(
                color: brandTheme.textColor1,
              ),
            ),
            Text(
              '$_counter',
              style: TextStyle(color: brandTheme.textColor1, fontSize: 200),
            ),
            Switch(
              key: isFirst ? switherGlobalKey : null,
              onChanged: (needDark) {
                oldTheme = brandTheme;
                BrandTheme.instanceOf(context).changeTheme(
                  needDark ? BrandThemeKey.dark : BrandThemeKey.light,
                );
              },
              value: BrandTheme.of(context).brightness == Brightness.dark,
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(
          Icons.add,
        ),
      ),
    );
  }

  @override
  void didUpdateWidget(Widget oldWidget) {
    var theme = BrandTheme.of(context);
    if (theme != oldTheme) {
      _getSwitcherCoodinates();
      _controller.reset();
      _controller.forward().then(
        (_) {
          oldTheme = theme;
        },
      );
    }
    super.didUpdateWidget(oldWidget);
  }

  void _getSwitcherCoodinates() {
    RenderBox renderObject = switherGlobalKey.currentContext.findRenderObject();
    switcherOffset = renderObject.localToGlobal(Offset.zero);
  }

  @override
  Widget build(BuildContext context) {
    var brandTheme = BrandTheme.of(context);

    if (oldTheme == null) {
      return _getPage(brandTheme, isFirst: true);
    }
    return Stack(
      children: <Widget>[
        if(oldTheme != null) _getPage(oldTheme),
        AnimatedBuilder(
          animation: _controller,
          child: _getPage(brandTheme, isFirst: true),
          builder: (_, child) {
            return ClipPath(
              clipper: MyClipper(
                sizeRate: _controller.value,
                offset: switcherOffset.translate(30, 15),
              ),
              child: child,
            );
          },
        ),
      ],
    );
  }
}

class MyClipper extends CustomClipper<Path> {
  MyClipper({this.sizeRate, this.offset});
  final double sizeRate;
  final Offset offset;

  @override
  Path getClip(Size size) {
    var path = Path()
      ..addOval(
        Rect.fromCircle(center: offset, radius: size.height * sizeRate),
      );

    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}

class BrandTheme extends StatefulWidget {
  final Widget child;

  BrandTheme({
    Key key,
    @required this.child,
  }) : super(key: key);

  @override
  BrandThemeState createState() => BrandThemeState();

  static BrandThemeModel of(BuildContext context) {
    final inherited =
        (context.dependOnInheritedWidgetOfExactType<_InheritedBrandTheme>());
    return inherited.data.brandTheme;
  }

  static BrandThemeState instanceOf(BuildContext context) {
    final inherited =
        (context.dependOnInheritedWidgetOfExactType<_InheritedBrandTheme>());
    return inherited.data;
  }
}

class BrandThemeState extends State<BrandTheme> {
  BrandThemeModel _brandTheme;

  BrandThemeModel get brandTheme => _brandTheme;

  @override
  void initState() {
    final isPlatformDark =
        WidgetsBinding.instance.window.platformBrightness == Brightness.dark;
    final themeKey = isPlatformDark ? BrandThemeKey.dark : BrandThemeKey.light;
    _brandTheme = BrandThemes.getThemeFromKey(themeKey);
    super.initState();
  }

  void changeTheme(BrandThemeKey themeKey) {
    setState(() {
      _brandTheme = BrandThemes.getThemeFromKey(themeKey);
    });
  }

  @override
  Widget build(BuildContext context) {
    return _InheritedBrandTheme(
      data: this,
      child: widget.child,
    );
  }
}

class _InheritedBrandTheme extends InheritedWidget {
  final BrandThemeState data;

  _InheritedBrandTheme({
    this.data,
    Key key,
    @required Widget child,
  }) : super(key: key, child: child);

  @override
  bool updateShouldNotify(_InheritedBrandTheme oldWidget) {
    return true;
  }
}

ThemeData defaultThemeData = ThemeData(
  floatingActionButtonTheme: FloatingActionButtonThemeData(
    shape: RoundedRectangleBorder(),
  ),
);

class BrandThemeModel extends Equatable {
  final Color color1;
  final Color color2;

  final Color textColor1;
  final Color textColor2;
  final ThemeData themeData;
  final Brightness brightness;

  BrandThemeModel({
    @required this.color1,
    @required this.color2,
    @required this.textColor1,
    @required this.textColor2,
    @required this.brightness,
  }) : themeData = defaultThemeData.copyWith(brightness: brightness);

  @override
  List<Object> get props => [
        color1,
        color2,
        textColor1,
        textColor2,
        themeData,
        brightness,
      ];
}

enum BrandThemeKey { light, dark }

class BrandThemes {
  static BrandThemeModel getThemeFromKey(BrandThemeKey themeKey) {
    switch (themeKey) {
      case BrandThemeKey.light:
        return lightBrandTheme;
      case BrandThemeKey.dark:
        return darkBrandTheme;
      default:
        return lightBrandTheme;
    }
  }
}

BrandThemeModel lightBrandTheme = BrandThemeModel(
  brightness: Brightness.light,
  color1: Colors.blue,
  color2: Colors.white,
  textColor1: Colors.black,
  textColor2: Colors.white,
);

BrandThemeModel darkBrandTheme = BrandThemeModel(
  brightness: Brightness.dark,
  color1: Colors.red,
  color2: Colors.black,
  textColor1: Colors.blue,
  textColor2: Colors.yellow,
);

class ThemeRoute extends PageRouteBuilder {
  ThemeRoute(this.widget)
      : super(
          pageBuilder: (
            context,
            animation,
            secondaryAnimation,
          ) =>
              widget,
          transitionsBuilder: transitionsBuilder,
        );

  final Widget widget;
}

Widget transitionsBuilder(
  BuildContext context,
  Animation<double> animation,
  Animation<double> secondaryAnimation,
  Widget child,
) {
  var _animation = Tween<double>(
    begin: 0,
    end: 100,
  ).animate(animation);
  return SlideTransition(
    position: Tween<Offset>(
      begin: const Offset(0, 1),
      end: Offset.zero,
    ).animate(animation),
    child: Container(
      child: child,
    ),
  );
}

这篇关于如何在颤动中添加主题切换动画?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆