import 'dart:async'; import 'package:flutter/material.dart'; import 'flip_side.dart'; import 'flip_card_controllers.dart'; import 'dart:math'; /// [FlipCard] A component that provides a flip card animation class FlipCard extends StatefulWidget { /// [controller] used to ccontrol the flip final FlipCardController controller; ///[frontWidget] The Front side widget of the card final Widget frontWidget; ///[backWidget] The Back side widget of the card final Widget backWidget; /// [onTapFlipping] When enabled, the card will flip automatically when touched. final bool onTapFlipping; /// [axis] The flip axis [Horizontal] and [Vertical] final FlipAxis axis; /// [rotateSide] The card rotate side final RotateSide rotateSide; /// [animationDuration] The amount of milliseconds a turn animation will take. final Duration animationDuration; /// [disableSplashEffect] The option for disable Inkwell widget's splash effect. final bool disableSplashEffect; /// [splashColor] The option for Inkwell widget's splashColor. final Color? splashColor; /// [focusColor] The option for Inkwell widget's focusColor. final Color? focusColor; const FlipCard({ Key? key, this.focusColor, this.splashColor, this.onTapFlipping = false, this.disableSplashEffect = false, required this.frontWidget, required this.backWidget, required this.controller, this.axis = FlipAxis.vertical, required this.rotateSide, this.animationDuration = const Duration(milliseconds: 800), }) : super(key: key); @override FlipCardState createState() => FlipCardState(); } class FlipCardState extends State with TickerProviderStateMixin { late AnimationController animationController; double anglePlus = 0; late bool _isFront; StreamSubscription? _sub; @override void initState() { super.initState(); _isFront = widget.controller.isFront; animationController = AnimationController(duration: widget.animationDuration, vsync: this); if (!_isFront) { animationController.value = 1.0; anglePlus = pi; } _sub ??= widget.controller.asStream.distinct().listen((isFront) async { if (this._isFront != isFront) { if (animationController.isAnimating) return; this._isFront = isFront; await animationController .forward(from: 0) .then((value) => anglePlus = pi); } }); } @override void dispose() { animationController.dispose(); _sub?.cancel(); _sub = null; super.dispose(); } @override Widget build(BuildContext context) => AnimatedBuilder( animation: animationController, builder: (context, child) { double piValue = 0.0; if (widget.rotateSide == RotateSide.top || widget.rotateSide == RotateSide.left) { piValue = pi; } else { piValue = -pi; } print('animation controller ${animationController.value}'); double angle = animationController.value * piValue; late Matrix4 transform; late Matrix4 transformForBack; if (_isFront) angle += anglePlus; if (widget.axis == FlipAxis.horizontal) { transform = Matrix4.identity() ..setEntry(3, 2, 0.001) ..rotateX(angle); transformForBack = Matrix4.identity()..rotateX(pi); } else { transform = Matrix4.identity() ..setEntry(3, 2, 0.001) ..rotateY(angle); transformForBack = Matrix4.identity()..rotateY(pi); } final isFrontWidget = _isFrontWidget(angle.abs()); return Transform( alignment: Alignment.center, transform: transform, child: isFrontWidget ? widget.frontWidget : Transform( transform: transformForBack, alignment: Alignment.center, child: widget.backWidget, ), ); }); bool _isFrontWidget(double angle) { const degrees90 = pi / 2; const degrees270 = 3 * pi / 2; return angle <= degrees90 || angle >= degrees270; } }