143 lines
4.1 KiB
Dart
143 lines
4.1 KiB
Dart
|
|
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<FlipCard> 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;
|
||
|
|
}
|
||
|
|
}
|