Анимация флаттера -переворачивания — переворачивание карты правой или левой стороной в зависимости от местоположения касания

#flutter #flutter-animation

#переворачивание #анимация переворачивания карты

Вопрос:

Я начал играть с Flutter и теперь думаю о том, как наилучшим образом реализовать анимацию переворачивания карты.

Я нашел этот пакет flip_card и пытаюсь приспособить его исходный код к своим потребностям.

Вот приложение, которое у меня есть сейчас:

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

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

class FlipAnimationApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("Flip animation"),
        ),
        body: Center(
          child: Container(
            width: 200,
            height: 200,
            child: WidgetFlipper(
              frontWidget: Container(
                color: Colors.green[200],
                child: Center(
                  child: Text(
                    "FRONT side.",
                  ),
                ),
              ),
              backWidget: Container(
                color: Colors.yellow[200],
                child: Center(
                  child: Text(
                    "BACK side.",
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class WidgetFlipper extends StatefulWidget {
  WidgetFlipper({
    Key key,
    this.frontWidget,
    this.backWidget,
  }) : super(key: key);

  final Widget frontWidget;
  final Widget backWidget;

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

class _WidgetFlipperState extends State<WidgetFlipper> with SingleTickerProviderStateMixin {
  AnimationController controller;
  Animation<double> _frontRotation;
  Animation<double> _backRotation;
  bool isFrontVisible = true;

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

    controller = AnimationController(duration: Duration(milliseconds: 500), vsync: this);
    _frontRotation = TweenSequence(
      <TweenSequenceItem<double>>[
        TweenSequenceItem<double>(
          tween: Tween(begin: 0.0, end: pi / 2).chain(CurveTween(curve: Curves.linear)),
          weight: 50.0,
        ),
        TweenSequenceItem<double>(
          tween: ConstantTween<double>(pi / 2),
          weight: 50.0,
        ),
      ],
    ).animate(controller);
    _backRotation = TweenSequence(
      <TweenSequenceItem<double>>[
        TweenSequenceItem<double>(
          tween: ConstantTween<double>(pi / 2),
          weight: 50.0,
        ),
        TweenSequenceItem<double>(
          tween: Tween(begin: -pi / 2, end: 0.0).chain(CurveTween(curve: Curves.linear)),
          weight: 50.0,
        ),
      ],
    ).animate(controller);
  }

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

  @override
  Widget build(BuildContext context) {
    return Stack(
      fit: StackFit.expand,
      children: [
        AnimatedCard(
          animation: _backRotation,
          child: widget.backWidget,
        ),
        AnimatedCard(
          animation: _frontRotation,
          child: widget.frontWidget,
        ),
        _tapDetectionControls(),
      ],
    );
  }

  Widget _tapDetectionControls() {
    return Stack(
      fit: StackFit.expand,
      children: <Widget>[
        GestureDetector(
          onTap: _leftRotation,
          child: FractionallySizedBox(
            widthFactor: 0.5,
            heightFactor: 1.0,
            alignment: Alignment.topLeft,
            child: Container(
              color: Colors.transparent,
            ),
          ),
        ),
        GestureDetector(
          onTap: _rightRotation,
          child: FractionallySizedBox(
            widthFactor: 0.5,
            heightFactor: 1.0,
            alignment: Alignment.topRight,
            child: Container(
              color: Colors.transparent,
            ),
          ),
        ),
      ],
    );
  }

  void _leftRotation() {
    _toggleSide();
  }

  void _rightRotation() {
    _toggleSide();
  }

  void _toggleSide() {
    if (isFrontVisible) {
      controller.forward();
      isFrontVisible = false;
    } else {
      controller.reverse();
      isFrontVisible = true;
    }
  }
}

class AnimatedCard extends StatelessWidget {
  AnimatedCard({
    this.child,
    this.animation,
  });

  final Widget child;
  final Animation<double> animation;

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: animation,
      builder: (BuildContext context, Widget child) {
        var transform = Matrix4.identity();
        transform.setEntry(3, 2, 0.001);
        transform.rotateY(animation.value);
        return Transform(
          transform: transform,
          alignment: Alignment.center,
          child: child,
        );
      },
      child: child,
    );
  }
}

  

Вот как это выглядит:

Чего я хотел бы добиться, чтобы карта переворачивалась на правую сторону, если ее касались правой половиной, и на левую сторону, если она касалась левой половины. Если ее нажимать несколько раз подряд на одну и ту же половину, она должна переворачиваться одной и той же стороной (не взад-вперед, как это делается сейчас).

Таким образом, желаемая анимация должна вести себя как следующая из приложения Quizlet.

Ответ №1:

Вы должны знать, когда вы нажимаете справа или слева, чтобы динамически изменять анимацию, для этого вы могли бы использовать флажок isRightTap . Затем вам следует инвертировать значения Tweens , если она должна поворачиваться в одну или другую сторону.

И сторона, которую вы должны повернуть, будет:

  • Поверните влево, если видна лицевая сторона, и вы нажали на левую, или, поскольку анимация обратной стороны перевернута, если видна задняя сторона, и вы нажали на правую
  • В противном случае поверните вправо

Вот что я изменил в _WidgetFlipperState из кода в вопросе:

 _updateRotations(bool isRightTap) {
  setState(() {
    bool rotateToLeft = (isFrontVisible amp;amp; !isRightTap) || !isFrontVisible amp;amp; isRightTap;
    _frontRotation = TweenSequence(
      <TweenSequenceItem<double>>[
        TweenSequenceItem<double>(
          tween: Tween(begin: 0.0, end: rotateToLeft ? (pi / 2) : (-pi / 2))
              .chain(CurveTween(curve: Curves.linear)),
          weight: 50.0,
        ),
        TweenSequenceItem<double>(
          tween: ConstantTween<double>(rotateToLeft ? (-pi / 2) : (pi / 2)),
          weight: 50.0,
        ),
      ],
    ).animate(controller);
    _backRotation = TweenSequence(
      <TweenSequenceItem<double>>[
        TweenSequenceItem<double>(
          tween: ConstantTween<double>(rotateToLeft ? (pi / 2) : (-pi / 2)),
          weight: 50.0,
        ),
        TweenSequenceItem<double>(
          tween: Tween(begin: rotateToLeft ? (-pi / 2) : (pi / 2), end: 0.0)
              .chain(CurveTween(curve: Curves.linear)),
          weight: 50.0,
        ),
      ],
    ).animate(controller);
  });
}

@override
void initState() {
  super.initState();
  controller =
      AnimationController(duration: Duration(milliseconds: 500), vsync: this);
  _updateRotations(true);
}

void _leftRotation() {
  _toggleSide(false);
}

void _rightRotation() {
  _toggleSide(true);
}

void _toggleSide(bool isRightTap) {
  _updateRotations(isRightTap);
  if (isFrontVisible) {
    controller.forward();
    isFrontVisible = false;
  } else {
    controller.reverse();
    isFrontVisible = true;
  }
}
  

Ответ №2:

введите описание изображения здесь

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

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp();

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage();

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

class _MyHomePageState extends State<MyHomePage> {
  bool _toggler = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(actions: [
        TextButton(
          onPressed: _onFlipCardPressed,
          child: const Text('change', style: TextStyle(color: Colors.white)),
        )
      ]),
      body: Center(
        child: SizedBox.square(
          dimension: 140,
          child: FlipCard(
            toggler: _toggler,
            frontCard: AppCard(title: 'Front'),
            backCard: AppCard(title: 'Back'),
          ),
        ),
      ),
    );
  }

  void _onFlipCardPressed() {
    setState(() {
      _toggler = !_toggler;
    });
  }
}

class AppCard extends StatelessWidget {
  final String title;

  const AppCard({
    required this.title,
  });

  @override
  Widget build(BuildContext context) {
    return DecoratedBox(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(20.0),
        color: Colors.deepPurple[400],
      ),
      child: Center(
        child: Text(
          title,
          style: const TextStyle(
            fontSize: 40.0,
            color: Colors.white,
          ),
          textAlign: TextAlign.center,
        ),
      ),
    );
  }
}

class FlipCard extends StatelessWidget {
  final bool toggler;
  final Widget frontCard;
  final Widget backCard;

  const FlipCard({
    required this.toggler,
    required this.backCard,
    required this.frontCard,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: AnimatedSwitcher(
        duration: const Duration(milliseconds: 800),
        transitionBuilder: _transitionBuilder,
        layoutBuilder: (widget, list) => Stack(children: [widget!, ...list]),
        switchInCurve: Curves.ease,
        switchOutCurve: Curves.ease.flipped,
        child: toggler
            ? SizedBox(key: const ValueKey('front'), child: frontCard)
            : SizedBox(key: const ValueKey('back'), child: backCard),
      ),
    );
  }

  Widget _transitionBuilder(Widget widget, Animation<double> animation) {
    final rotateAnimation = Tween(begin: pi, end: 0.0).animate(animation);
    return AnimatedBuilder(
      animation: rotateAnimation,
      child: widget,
      builder: (context, widget) {
        final isFront = ValueKey(toggler) == widget!.key;
        final rotationY = isFront ? rotateAnimation.value : min(rotateAnimation.value, pi * 0.5);
        return Transform(
          transform: Matrix4.rotationY(rotationY)..setEntry(3, 0, 0),
          alignment: Alignment.center,
          child: widget,
        );
      },
    );
  }
}
  

Ответ №3:

Попробуйте этот код Я внес некоторые изменения в ваш код, теперь GestureDetector ширина виджета разделена поровну, поэтому, когда вы нажимаете на левую часть окна, анимация меняется на противоположную, а если вы нажимаете на правую часть, анимация перенаправляется.

  Widget _tapDetectionControls() {
    return Flex(
      direction: Axis.horizontal,
      children: <Widget>[
        Expanded(
          flex: 1,
          child: GestureDetector(
            onTap: _leftRotation,
          ),
        ),
        Expanded(
          flex: 1,
          child: GestureDetector(
            onTap: _rightRotation,
          ),
        ),
      ],
    );
  }

  void _leftRotation() {
    controller.reverse();
  }

  void _rightRotation() {
    controller.forward();
  }
  

Комментарии:

1. Я пробовал аналогичный подход, но он не работает / не удовлетворяет моим потребностям: Пример 1. Если вы нажмете на правую сторону карты, она перевернется, но если вы нажмете на правую сторону еще раз, это ничего не изменит, поскольку анимация уже находится в конечном состоянии. Пример 2. Если вы откроете приложение и нажмете на левую сторону карты, if не перевернется, поскольку анимация в начальной фазе и обратном порядке не вступит в силу. Есть предложения?

2. итак, когда мы нажимаем на правую сторону, карта должна переворачиваться вперед, а когда мы нажимаем на левую сторону, она должна переворачиваться назад, независимо от того, первая она или последняя? Правильно??

3. Да, именно такого поведения я хотел бы добиться.

4. Я пробовал этот сценарий, поэтому в этом нам нужны две анимации для обоих из 2 виджетов, которые должны динамически изменяться. Я попытаюсь взломать это и дам вам знать.

5. Я пришел к тому же выводу, но у меня пока недостаточно навыков во Flutter, чтобы решить эту проблему элегантным способом 🙂