android - flutter 背景动画

标签 android dart flutter

我正在使用 Flutter 工作一点点,我了解除动画以外的所有内容(我从不喜欢使用动画)。

我已经尝试实现 Backdrop在我的 Flutter 应用程序中使用这个 Flutter Demo .实现背景很容易。

我坚持实现背景导航,让它通过汉堡包按钮上下滑动。 我读过 Animations in Flutter教程。我了解动画的基础知识( Controller 、动画等)。但在这个背景示例中,它有点不同。

有人可以一步步向我解释这个案例吗?谢谢。

最佳答案

结合以下链接的信息,我设法找到了解决方案。请注意,我不是 Flutter 方面的专家(也不是动画)。话虽如此,非常感谢您提出建议和更正。

创建 backdrop.dart 文件后,转到 _BackdropTitle 类并编辑为菜单和关闭图标定义 IconButton 的部分。您必须在包含图标的 Opacity 项中执行旋转:

icon: Stack(children: <Widget>[
  new Opacity(
    opacity: new CurvedAnimation(
      parent: new ReverseAnimation(animation),
      curve: const Interval(0.5, 1.0),
    ).value,
    child: new AnimatedBuilder(
      animation: animationController,
      child: new Container(
        child: new Icon(Icons.close),
      ),
      builder: (BuildContext context, Widget _widget) {
        return new Transform.rotate(
          angle: animationController.value * -6.3,
          child: _widget,
        );
      },
    ),
  ),
  new Opacity(
    opacity: new CurvedAnimation(
      parent: animation,
      curve: const Interval(0.5, 1.0),
    ).value,
    child: new AnimatedBuilder(
      animation: animationController,
      child: new Container(
        child: new Icon(Icons.menu),
      ),
      builder: (BuildContext context, Widget _widget) {
        return new Transform.rotate(
          angle: animationController.value * 6.3,
          child: _widget,
        );
      },
    ),
  ),
],

我不得不在关闭变换的角度中使用负值,以便将其旋转到与菜单动画相同的方向。

完整代码如下:

import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'menu.dart';

const double _kFlingVelocity = 2.0;

class Backdrop extends StatefulWidget {
  final MenuCategory currentCategory;
  final Widget frontLayer;
  final Widget backLayer;
  final Widget frontTitle;
  final Widget backTitle;

  const Backdrop({
    @required this.currentCategory,
    @required this.frontLayer,
    @required this.backLayer,
    @required this.frontTitle,
    @required this.backTitle,
  })  : assert(currentCategory != null),
    assert(frontLayer != null),
    assert(backLayer != null),
    assert(frontTitle != null),
    assert(backTitle != null);

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

class _BackdropState extends State<Backdrop>
  with SingleTickerProviderStateMixin {
  final GlobalKey _backdropKey = GlobalKey(debugLabel: 'Backdrop');

  AnimationController _controller;

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

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

@override
void didUpdateWidget(Backdrop old) {
  super.didUpdateWidget(old);

  if (widget.currentCategory != old.currentCategory) {
    _toggleBackdropLayerVisibility();
  } else if (!_frontLayerVisible) {
    _controller.fling(velocity: _kFlingVelocity);
  }
}

Widget _buildStack(BuildContext context, BoxConstraints constraints) {
  const double layerTitleHeight = 48.0;
  final Size layerSize = constraints.biggest;
  final double layerTop = layerSize.height - layerTitleHeight;

  Animation<RelativeRect> layerAnimation = RelativeRectTween(
    begin: RelativeRect.fromLTRB(
        0.0, layerTop, 0.0, layerTop - layerSize.height),
    end: RelativeRect.fromLTRB(0.0, 0.0, 0.0, 0.0),
  ).animate(_controller.view);

  return Stack(
    key: _backdropKey,
    children: <Widget>[
      widget.backLayer,
      PositionedTransition(
        rect: layerAnimation,
        child: _FrontLayer(
          onTap: _toggleBackdropLayerVisibility,
          child: widget.frontLayer,
        ),
      ),
    ],
  );
}

@override
Widget build(BuildContext context) {
  var appBar = AppBar(
    brightness: Brightness.light,
    elevation: 0.0,
    titleSpacing: 0.0,
    title: _BackdropTitle(
      animationController: _controller,
      onPress: _toggleBackdropLayerVisibility,
      frontTitle: widget.frontTitle,
      backTitle: widget.backTitle,
    ),
    actions: <Widget>[
      IconButton(
        icon: Icon(Icons.search),
        onPressed: () {
          // TODO
        },
      )
    ],
  );
  return Scaffold(
    appBar: appBar,
    body: LayoutBuilder(builder: _buildStack)
  );
}

bool get _frontLayerVisible {
  final AnimationStatus status = _controller.status;
  return status == AnimationStatus.completed ||
      status == AnimationStatus.forward;
}

void _toggleBackdropLayerVisibility() {
  _controller.fling(
      velocity: _frontLayerVisible ? -_kFlingVelocity : _kFlingVelocity);
  }
}

class _FrontLayer extends StatelessWidget {
  const _FrontLayer({
    Key key,
    this.onTap,
    this.child,
  }) : super(key: key);

  final VoidCallback onTap;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return Material(
      elevation: 16.0,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.only(
          topLeft: Radius.circular(16.0),
          topRight: Radius.circular(16.0)
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: <Widget>[
          GestureDetector(
            behavior: HitTestBehavior.opaque,
            onTap: onTap,
            child: Container(
              height: 40.0,
              alignment: AlignmentDirectional.centerStart,
            ),
          ),
          Expanded(
            child: child,
          ),
        ],
      ),
    );
  }
}

class _BackdropTitle extends AnimatedWidget {
  final AnimationController animationController;
  final Function onPress;
  final Widget frontTitle;
  final Widget backTitle;

  _BackdropTitle({
    Key key,
    this.onPress,
    @required this.frontTitle,
    @required this.backTitle,
    @required this.animationController,
  })  : assert(frontTitle != null),
    assert(backTitle != null),
    assert(animationController != null),
    super(key: key, listenable: animationController.view);

  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = this.listenable;

    return DefaultTextStyle(
      style: Theme.of(context).primaryTextTheme.title,
      softWrap: false,
      overflow: TextOverflow.ellipsis,
      child: Row(children: <Widget>[
        // branded icon
        SizedBox(
          width: 72.0,
          child: IconButton(
            padding: EdgeInsets.only(right: 8.0),
            onPressed: this.onPress,
            icon: Stack(children: <Widget>[
              new Opacity(
                opacity: new CurvedAnimation(
                  parent: new ReverseAnimation(animation),
                  curve: const Interval(0.5, 1.0),
                ).value,
                child: new AnimatedBuilder(
                  animation: animationController,
                  child: new Container(
                    child: new Icon(Icons.close),
                  ),
                  builder: (BuildContext context, Widget _widget) {
                    return new Transform.rotate(
                      angle: animationController.value * -6.3,
                      child: _widget,
                    );
                  },
                ),
              ),
              new Opacity(
                opacity: new CurvedAnimation(
                  parent: animation,
                  curve: const Interval(0.5, 1.0),
                ).value,
                child: new AnimatedBuilder(
                  animation: animationController,
                  child: new Container(
                    child: new Icon(Icons.menu),
                  ),
                  builder: (BuildContext context, Widget _widget) {
                    return new Transform.rotate(
                      angle: animationController.value * 6.3,
                      child: _widget,
                    );
                  },
                ),
              ),
            ]),
          ),
        ),
        // Here, we do a custom cross fade between backTitle and frontTitle.
        // This makes a smooth animation between the two texts.
        Stack(
          children: <Widget>[
            Opacity(
              opacity: CurvedAnimation(
                parent: ReverseAnimation(animation),
                curve: Interval(0.5, 1.0),
              ).value,
              child: FractionalTranslation(
                translation: Tween<Offset>(
                  begin: Offset.zero,
                  end: Offset(0.5, 0.0),
                ).evaluate(animation),
                child: backTitle,
              ),
            ),
            Opacity(
              opacity: CurvedAnimation(
                parent: animation,
                curve: Interval(0.5, 1.0),
              ).value,
              child: FractionalTranslation(
                translation: Tween<Offset>(
                  begin: Offset(-0.25, 0.0),
                  end: Offset.zero,
                ).evaluate(animation),
                child: frontTitle,
              ),
            ),
          ],
        )
      ]),
    );
  }
}

关于android - flutter 背景动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50332870/

相关文章:

Flutter未编译错误错误: The method 'CustomStyle.copyWith' has fewer named arguments than those of overridden method 'TextStyle.copyWith'

dart - 如何在 Windows 上将 Dart 与 Webstorm 6 结合使用(从 Google IO 2013 运行 Codelab)

java - 无法使用 OpenTok 在耳机模式下切换音频

java - 将 JSON 响应映射到 POJO(使用不同的名称)

android - 如何检测Android中的键盘动画结束?或者立即隐藏没有动画的键盘

dart - 无法使用 Dart-Polymer 中的自动绑定(bind) dart 模板内的选择器进行查询

flutter - 我需要 Flutter Firebase AdMob 包的哪些 MobileAdTargetingInfo 属性才能发布?

android - 您的主要 Activity 类 com.ryanheise.audioservice.AudioServiceActivity 不是 FlutterFragmentActivity 的子类

android - 在 Firestore 中阅读用户未阅读的信件

android - 如何以编程方式在外部 SD 卡上安装应用程序?