I have spent my whole day trying to add a simple button that would perform the same action as a swipe right on the cards in the following flutter widget. I however can't seem to make it work. Here is my code for now:
class TinderCards2 extends StatefulWidget {
#override
_TinderCards2State createState() => _TinderCards2State();
}
class _TinderCards2State extends State<TinderCards2> with SingleTickerProviderStateMixin {
// Add a counter variable
int counter = 0;
late AnimationController _animationController = AnimationController(
vsync: this, // pass the SingleTickerProviderStateMixin instance as the vsync parameter
duration: Duration(milliseconds: 500),
);
List<AnimatedPositioned> cards = [
AnimatedPositioned(left: 0, top: 0, duration: Duration(milliseconds: 500),
child: Card(
child: Container(
width: 400.0,height: 400.0,decoration: BoxDecoration(
image: DecorationImage(image: AssetImage("assets/1.png"),fit: BoxFit.cover)),
padding: EdgeInsets.all(16.0)), ),
),
AnimatedPositioned(left: 0, top: 0, duration: Duration(milliseconds: 500),
child: Card(
child: Container(
width: 400.0,height: 400.0,decoration: BoxDecoration(
image: DecorationImage(image: AssetImage("assets/2.png"),fit: BoxFit.cover,),),
padding: EdgeInsets.all(16.0)), ),
),
];
#override
int currentCardIndex = 0;
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Container(
// Set the background color to pink
color: Colors.pink,
child: Center(
// Wrap the Stack widget in a Container
child: Container(
// Set the height and width to a larger value
height: 400.0,
width: 400.0,
// Use a Stack widget to stack the cards on top of each other
child: Stack(
children: cards.map((card) {
return GestureDetector(
// Handle swipe gestures
onPanUpdate: (details) {
// Check the direction of the swipe
if (details.delta.dx > 0) { // Use last card's name
// Swiped right
setState(() {
int index = cards.indexOf(card);
currentCardIndex = index;
cards.removeAt(index);
cards.insert(index, AnimatedPositioned(left: 1000, top: 0, duration: Duration(milliseconds: 500), child: card.child));
right2.add(index);
;});
} else if (details.delta.dx < 0) {
// Swiped left
setState(() {
int index = cards.indexOf(card);
currentCardIndex = index;
cards.removeAt(index);
cards.insert(index, AnimatedPositioned(left: -1000, top: 0, duration: Duration(milliseconds: 500), child: card.child));
left2.add(index);
;});
} /*else if (details.delta.dy > 0) {
setState(() {
int index = cards.indexOf(card);
currentCardIndex = index;
cards.removeAt(index);
cards.insert(index, AnimatedPositioned(left: 0, top: 2000, duration: Duration(milliseconds: 500), child: card.child));
down2.add(index);
});
}*/;
},
onPanEnd: (details) {
if (currentCardIndex == 0) {
// Navigate to a new screen
Navigator.pushNamed(context, '/GroupGame');
}
},
child: Stack(
children: [
card,
],
),
);
}).toList(),
alignment: Alignment.center,
)
),
),
);
}
}
Do you know what I could try? Any help is greatly appreciated.
I tried wrapping the Stack widget containing the swipeable cards in a Column widget and adding a TextButton widget as the last child but never got it to work.
Can't help you at all with your specific question, but without any other context I'd suggest you to not try to reinvent the wheel. There are many packages already doing Tinderlike cards. I'd suggest this one specifically since it is the most similar and with many customizable options.
https://pub.dev/packages/swipable_stack
Related
I am building an app that allows the user to build their gyms floorplan and place buttons where the machines would be.
I am currently using an Interactive Viewer to display the floorplan. I need the functionality to create a button widget where the "User" long-presses.
Here is my Current Code with the onLongPressEnd class.
Padding(
padding: const EdgeInsets.symmetric(vertical: 1),
child: Center(
child: GestureDetector(
onLongPressEnd: ((details) => {
//Code to create button goes here
}),
child: InteractiveViewer(
minScale: 1,
maxScale: 2,
child: Stack(
children: [
Image.asset(
'Assets/Lift View Images/room_layout_1.png'),
);
Someone on the internet has posted an almost identical problem to this and created a whole solution, the problem is since his solution, the InteractiveViewer widget has been added so all of his code is obsolete and cannot be copied to a newer version.
https://medium.com/#alexander.arendar/dragging-zooming-and-placing-object-on-indoor-map-with-flutter-67667ef415ec
In conclusion I need the functionality for the user to create pre defined widgets by pressing on the page.
Full Code https://github.com/CokeMango/mapview_iteration_1.git
I tried for hours to understand this documents solution, https://medium.com/#alexander.arendar/dragging-zooming-and-placing-object-on-indoor-map-with-flutter-67667ef415ec but never figured out how to implement it with the Interactive Viewer widget. And being fairly new to Flutter I couldn't replicate it exactly.
I also searched online for a while and at most I found what I already had which was a zoomable scrollable image viewer with no functionality.
here you can see two approaches: FloorPlanWithFixedButtons adds "buttons" with a fixed size (no matter what the current zoom factor is used by InteractiveViewer), while FloorPlanWithScaledButtons adds "buttons" directly to InteractiveViewer so they are automatically scaled when you zoom-in / zoom-out
if you need draggable "buttons" check FloorPlanWithScaledDraggableButtons widget
class ChipEntry {
ChipEntry({
required this.offset,
required this.label,
});
Offset offset;
final String label;
}
FloorPlanWithFixedButtons
class FloorPlanWithFixedButtons extends StatefulWidget {
#override
State<FloorPlanWithFixedButtons> createState() => _FloorPlanWithFixedButtonsState();
}
class _FloorPlanWithFixedButtonsState extends State<FloorPlanWithFixedButtons> with TickerProviderStateMixin {
int animatedIndex = -1;
late final controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 250));
final chips = <ChipEntry>[];
final transformationController = TransformationController();
int labelNumber = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
const Padding(
padding: EdgeInsets.all(8.0),
child: Text('1) long press on the floor plan below to add a new button\n'),
),
Expanded(
child: ClipRect(
child: GestureDetector(
onLongPressStart: _addButton,
child: Stack(
children: [
InteractiveViewer(
minScale: 1,
maxScale: 5,
constrained: false,
transformationController: transformationController,
// https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Sample_Floorplan.jpg/640px-Sample_Floorplan.jpg
child: Image.asset('images/640px-Sample_Floorplan.jpg'),
),
CustomMultiChildLayout(
delegate: FloorPlanDelegate(
chips: chips,
transformationController: transformationController,
),
children: [
for (int index = 0; index < chips.length; index++)
LayoutId(id: index, child: _button(index)),
],
),
],
),
),
),
),
],
);
}
Widget _button(int index) {
final button = Chip(
backgroundColor: Colors.orange,
side: const BorderSide(width: 1, color: Colors.black12),
elevation: 4,
onDeleted: () async {
setState(() {
animatedIndex = index;
});
await controller.reverse(from: 1.0);
setState(() {
chips.removeAt(index);
animatedIndex = -1;
});
},
label: InkWell(
onTap: () => print('button |${chips[index].label}| at index $index pressed'),
child: Text(chips[index].label),
),
);
return index == animatedIndex? ScaleTransition(scale: controller, child: button) : button;
}
void _addButton(LongPressStartDetails details) async {
setState(() {
animatedIndex = chips.length;
final chipEntry = ChipEntry(
offset: transformationController.toScene(details.localPosition),
label: 'btn #$labelNumber'
);
chips.add(chipEntry);
labelNumber++;
});
await controller.forward(from: 0.0);
animatedIndex = -1;
}
}
class FloorPlanDelegate extends MultiChildLayoutDelegate {
FloorPlanDelegate({
required this.chips,
required this.transformationController,
}) : super(relayout: transformationController); // NOTE: this is very important
final List<ChipEntry> chips;
final TransformationController transformationController;
#override
void performLayout(ui.Size size) {
// print('performLayout $size');
int id = 0;
final constraints = BoxConstraints.loose(size);
final matrix = transformationController.value;
for (final chip in chips) {
final size = layoutChild(id, constraints);
final offset = MatrixUtils.transformPoint(matrix, chip.offset) - size.center(Offset.zero);
positionChild(id, offset);
id++;
}
}
#override
bool shouldRelayout(covariant MultiChildLayoutDelegate oldDelegate) => false;
}
FloorPlanWithScaledButtons
class FloorPlanWithScaledButtons extends StatefulWidget {
#override
State<FloorPlanWithScaledButtons> createState() => _FloorPlanWithScaledButtonsState();
}
class _FloorPlanWithScaledButtonsState extends State<FloorPlanWithScaledButtons> with TickerProviderStateMixin {
int animatedIndex = -1;
late final controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 250));
final chips = <ChipEntry>[];
final transformationController = TransformationController();
int labelNumber = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
const Padding(
padding: EdgeInsets.all(8.0),
child: Text('1) long press on the floor plan below to add a new button\n'),
),
Expanded(
child: ClipRect(
child: GestureDetector(
onLongPressStart: _addButton,
child: InteractiveViewer(
minScale: 1,
maxScale: 5,
constrained: false,
transformationController: transformationController,
child: Stack(
children: [
// https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Sample_Floorplan.jpg/640px-Sample_Floorplan.jpg
Image.asset('images/640px-Sample_Floorplan.jpg'),
...chips.mapIndexed(_positionedButton),
],
),
),
),
),
),
],
);
}
Widget _positionedButton(int index, ChipEntry chip) {
final child = Chip(
backgroundColor: Colors.orange,
side: const BorderSide(width: 1, color: Colors.black12),
elevation: 4,
onDeleted: () async {
setState(() {
animatedIndex = index;
});
await controller.reverse(from: 1.0);
setState(() {
chips.removeAt(index);
animatedIndex = -1;
});
},
label: InkWell(
onTap: () => print('button |${chip.label}| at index $index pressed'),
child: Text(chip.label),
),
);
return Positioned(
left: chip.offset.dx,
top: chip.offset.dy,
child: FractionalTranslation(
translation: const Offset(-0.5, -0.5),
child: index == animatedIndex? ScaleTransition(scale: controller, child: child) : child,
),
);
}
void _addButton(LongPressStartDetails details) async {
setState(() {
animatedIndex = chips.length;
final chipEntry = ChipEntry(
offset: transformationController.toScene(details.localPosition),
label: 'btn #$labelNumber'
);
chips.add(chipEntry);
labelNumber++;
});
await controller.forward(from: 0.0);
animatedIndex = -1;
}
}
FloorPlanWithScaledDraggableButtons
class FloorPlanWithScaledDraggableButtons extends StatefulWidget {
#override
State<FloorPlanWithScaledDraggableButtons> createState() => _FloorPlanWithScaledDraggableButtonsState();
}
class _FloorPlanWithScaledDraggableButtonsState extends State<FloorPlanWithScaledDraggableButtons> {
final chips = <ChipEntry>[];
final transformationController = TransformationController();
int labelNumber = 0;
#override
Widget build(BuildContext context) {
return Column(
children: [
const Padding(
padding: EdgeInsets.all(8.0),
child: Text('1) long press on the floor plan below to add a new button\n'
'2) long press on the added button to drag it'),
),
Expanded(
child: ClipRect(
child: GestureDetector(
onLongPressStart: _addButton,
child: InteractiveViewer(
minScale: 1,
maxScale: 5,
constrained: false,
transformationController: transformationController,
child: Stack(
children: [
// https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Sample_Floorplan.jpg/640px-Sample_Floorplan.jpg
Image.asset('images/640px-Sample_Floorplan.jpg'),
...chips.mapIndexed(_button),
],
),
),
),
),
),
],
);
}
Widget _button(int index, ChipEntry chip) {
return DraggableChip(
chip: chip,
onTap: () => print('button |${chip.label}| at index $index pressed'),
onDrag: (delta) => setState(() => chip.offset += _scaled(delta)),
onDeleted: () => setState(() => chips.removeAt(index)),
);
}
Offset _scaled(Offset delta) {
return delta / transformationController.value.getMaxScaleOnAxis();
}
void _addButton(LongPressStartDetails details) {
setState(() {
final chipEntry = ChipEntry(
offset: transformationController.toScene(details.localPosition),
label: 'btn #$labelNumber'
);
chips.add(chipEntry);
labelNumber++;
});
}
}
class DraggableChip extends StatefulWidget {
const DraggableChip({
Key? key,
required this.chip,
this.onTap,
this.onDrag,
this.onDeleted,
}) : super(key: key);
final ChipEntry chip;
final VoidCallback? onTap;
final Function(Offset)? onDrag;
final VoidCallback? onDeleted;
#override
State<DraggableChip> createState() => _DraggableChipState();
}
class _DraggableChipState extends State<DraggableChip> with SingleTickerProviderStateMixin {
late final controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 250));
bool drag = false;
Offset position = Offset.zero;
double scale = 0;
#override
void initState() {
super.initState();
controller.forward();
}
#override
void didUpdateWidget(covariant DraggableChip oldWidget) {
super.didUpdateWidget(oldWidget);
scale = controller.value = 1;
}
#override
Widget build(BuildContext context) {
final child = RawChip(
selected: drag,
showCheckmark: false,
selectedColor: Colors.teal,
backgroundColor: Colors.orange,
side: const BorderSide(width: 1, color: Colors.black12),
elevation: 4,
onDeleted: () async {
await controller.reverse();
widget.onDeleted?.call();
},
label: GestureDetector(
onLongPressStart: (d) => setState(() {
drag = true;
position = d.globalPosition;
}),
onLongPressMoveUpdate: (d) {
widget.onDrag?.call(d.globalPosition - position);
position = d.globalPosition;
},
onLongPressEnd: (d) => setState(() => drag = false),
child: InkWell(
onTap: widget.onTap,
child: Text(widget.chip.label),
),
),
);
return Positioned(
left: widget.chip.offset.dx,
top: widget.chip.offset.dy,
child: FractionalTranslation(
translation: const Offset(-0.5, -0.5),
child: ScaleTransition(
scale: controller,
child: child,
),
),
);
}
}
How do I end the drop with proper velocity to a given offset
I am trying make something similar to SlidingUpPanel, but can't figure out how to implement.
final bottom = ValueNotifier<double>(0.0);
Stack(
children: [
ValueListenableBuilder<double>(
valueListenable: bottom,
builder: (context, value, child) => Positioned(
bottom: value,
left: 0,
right: 0,
child: GestureDetector(
onVerticalDragStart: (value) {
},
onVerticalDragUpdate: (DragUpdateDetails value) {
bottom.value -= value.delta.dy;
},
onVerticalDragEnd: (value) {
const double minFlingVelocity = 365.0;
if (value.velocity.pixelsPerSecond.dy.abs() >=
minFlingVelocity) {
// bottom.value =
// MediaQuery.of(context).size.height / 2;
}
},
child: Container(
width: double.maxFinite,
height: 200,
color: Colors.green,
),
),
),
)
],
),
I have a request function and I want to show a progress bar before loading data but idk how to do that. can someone show me an example?
This code calls your function after running the linear progress indicator for a specified time.
The script makes use of no external libraries
import 'dart:async';
import 'package:flutter/material.dart';
class ProgressBarCall extends StatefulWidget {
const ProgressBarCall({ Key? key }) : super(key: key);
#override
_ProgressBarCallState createState() => _ProgressBarCallState();
}
class _ProgressBarCallState extends State<ProgressBarCall> {
double _value = 0;
#override
Widget build(BuildContext context) {
checkIndicator(delay: 2);
return Scaffold(
body: Column(
children: [
LinearProgressIndicator(
backgroundColor: Colors.grey,
color: Colors.green,
minHeight: 5,
value: _value,
),
Expanded(
child: Container(child: Text("Perform function after loading"),),
),
],
),
);
}
void checkIndicator({delay = 2}){
new Timer.periodic(
Duration(milliseconds: delay*100),
(Timer timer){
setState(() {
if(_value == 1) {
timer.cancel();
performFunction();
}
else {
_value = _value + 0.1;
}
});
}
);
}
void performFunction(){
//call your function after the loading
}
}
The performFunction() method can be used to load your data Set the duration of the linear progress indicator by setting the delay in the checkIndicator() method.
You can implement flutter_easyloading package, https://pub.dev/packages/flutter_easyloading
for the progress widget is self you can use [CircularProgressIndicator] https://api.flutter.dev/flutter/material/CircularProgressIndicator-class.html)
for managing the state show loading -> then the actual data -> in case of failure show the error message and stop loading
this can be achieved throw many different ways
1- the easies one FutureBuilder
FutureBuilder<String>(
future: _calculation, // a previously-obtained Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
List<Widget> children;
if (snapshot.hasData) {
children = <Widget>[
const Icon(
Icons.check_circle_outline,
color: Colors.green,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Result: ${snapshot.data}'),
)
];
} else if (snapshot.hasError) {
children = <Widget>[
const Icon(
Icons.error_outline,
color: Colors.red,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
)
];
} else {
children = const <Widget>[
SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting result...'),
)
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: children,
),
);
},
);
or you can use any state management you want
bloc (example)[https://bloclibrary.dev/#/flutterweathertutorial]
I am building a language app, when the student is learning a new word, each syllable in the new word will be repeated three times. I have three progress bars to indicate to the student how long they have to wait until the next repetition.
Each progress bar has a duration of 3 seconds. When the first progress bar completes, it should trigger the second progress bar, when the second finishes it should trigger the third progress bar.
When the third bar completes, it should reset all of the progress bars and restart the process all over again.
The key here is to sequence the animation of each progress bar so that they can call a function on complete. This is the beginning of my problem.
I don't know how to get the progress bars to animate in sequence and to call a function when they are complete.
How do I go about achieving this with flutter? I already built this app with KivyMD but now I have to rebuild it with Flutter. However, KivyMD animations are simple but Flutter animations are complicated.
import 'package:blog/screens/widgets/nav_menu.dart';
import 'package:flutter/material.dart';
class AnimationTest3 extends StatefulWidget {
const AnimationTest3({Key? key}) : super(key: key);
#override
_AnimationTest3State createState() => _AnimationTest3State();
}
class _AnimationTest3State extends State<AnimationTest3>
with TickerProviderStateMixin {
late AnimationController _controller;
double progress1 = 0;
double progress2 = 0;
double progress3 = 0;
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(child: NavMenu()),
appBar: AppBar(title: Text("Animation Test 3"), elevation: 0),
body: LayoutBuilder(
builder: (context, constraints) {
return Column(children: [
Container(
height: constraints.maxHeight * 0.3, color: Colors.indigo[50]),
Container(height: constraints.maxHeight * 0.4, color: Colors.white),
Container(
height: constraints.maxHeight * 0.3,
color: Colors.indigo[50],
child: LayoutBuilder(
builder: (context, constraints) {
return Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// PROGRESS BAR #1
Container(
padding: EdgeInsets.symmetric(horizontal: 2.0),
height: 4,
width: constraints.maxWidth * 1 / 3,
child: LinearProgressIndicator(value: progress1),
),
Container(
// PROGRESS BAR #2
padding: EdgeInsets.symmetric(horizontal: 2.0),
height: 4,
width: constraints.maxWidth * 1 / 3,
child: LinearProgressIndicator(value: progress2),
),
Container(
// PROGRESS BAR #3
padding: EdgeInsets.symmetric(horizontal: 2.0),
height: 4,
width: constraints.maxWidth * 1 / 3,
child: LinearProgressIndicator(value: progress3),
),
],
);
},
),
)
]);
},
),
);
}
}
look into this reference for staggered animation, staggered animation, your situation should be:
controller = AnimationController(
duration: const Duration(milliseconds: 9000), vsync: this);
progress1 = Tween<double>(
begin: 0.0,
end: 0.3,
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.0,
0.33,
curve: Curves.ease,
),
),
),
progress2 = Tween<double>(
begin: 0.0,
end: 0.3,
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.33,
0.66,
curve: Curves.ease,
),
),
),
progress3 = Tween<double>(
begin: 0.0,
end: 0.3,
).animate(
CurvedAnimation(
parent: controller,
curve: const Interval(
0.66,
1.0,
curve: Curves.ease,
),
),
),
I have a flashcard app using flutter in android studio, I am very new at android studio and flutter. So every time the user flips, it shows the back side of the card. If the card is showing the back side while the user is pressing the next button, the card automatically shows the back side of the proceeding cards. What i wanted to do is only show the front side each time the user presses the next button.
I have 3 classes, a flip_card.dart [my animations], Flash.dart [buttons, widgets, front/back image lists, etc.], main.dart [main, only calls the class].
I do not know what to do, either call the animation when it is played to a function? or to call a local "bool isFront" inside the animation when it is played.
this is my whole flip_dart.dart
library flip_card;
import 'dart:math';
import 'package:flutter/material.dart';
import 'Flash.dart';
enum FlipDirection {
VERTICAL,
HORIZONTAL,
}
class AnimationCard extends StatelessWidget {
AnimationCard({this.child, this.animation, this.direction});
final Widget child;
final Animation<double> animation;
final FlipDirection direction;
#override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget child) {
var transform = Matrix4.identity();
transform.setEntry(3, 2, 0.001);
if (direction == FlipDirection.VERTICAL) {
transform.rotateX(animation.value);
} else {
transform.rotateY(animation.value);
}
return Transform(
transform: transform,
alignment: Alignment.center,
child: child,
);
},
child: child,
);
}
}
class FlipCard extends StatefulWidget {
final Widget front;
final Widget back;
final int speed = 500;
final FlipDirection direction;
const FlipCard(
{Key key,
#required this.front,
#required this.back,
this.direction = FlipDirection.HORIZONTAL})
: super(key: key);
#override
State<StatefulWidget> createState() {
return _FlipCardState();
}
}
class _FlipCardState extends State<FlipCard>
with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> _frontRotation;
Animation<double> _backRotation;
bool isFront = true;
#override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(milliseconds: widget.speed), 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);
}
_toggleCard() {
if (isFront) {
controller.forward();
// if(_nextImage()){
//
// }
} else {
controller.reverse();
}
isFront = !isFront;
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: _toggleCard,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
AnimationCard(
animation: _frontRotation,
child: widget.front,
direction: widget.direction,
),
AnimationCard(
animation: _backRotation,
child: widget.back,
direction: widget.direction,
),
],
),
);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
}
And this is my Flash.dart
import 'flip_card.dart';
import 'package:flutter/material.dart';
class Flashcard extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<Flashcard> {
int photoIndex = 0; //startfirstpage
bool isFront = true;
var playerProgress = 0;
List<String> photos = [ //front
'assets/c1v1f.png',
'assets/c1v2f.png',
'assets/c1v3f.png',
'assets/c1v4f.png',
'assets/c1v5f.png',
'assets/c1v6f.png',
'assets/c1v7f.png',
'assets/c1v8f.png',
'assets/c1v9f.png',
'assets/c1v10f.png',
];
List<String> photos1 = [ //back
'assets/c1v1b.png',
'assets/c1v2b.png',
'assets/c1v3b.png',
'assets/c1v4b.png',
'assets/c1v5b.png',
'assets/c1v6b.png',
'assets/c1v7b.png',
'assets/c1v8b.png',
'assets/c1v9b.png',
'assets/c1v10b.png',
];
var bookmarkicon = "assets/bookmarkoff.png";
var bookmarkOff = "assets/bookmarkoff.png";
var bookmarkOn = "assets/bookmarkon.png";
void _previousImage() { // prev
setState(() {
photoIndex = photoIndex > 0 ? photoIndex - 1 : 0;
});
}
void _nextImage() {
// next
setState(() {
photoIndex = photoIndex < photos.length - 1 ? photoIndex + 1 : photoIndex;
playerProgress += 10;
print(playerProgress);
print(photoIndex);
// if(isFront){
// photoIndex--;
// }
Flashcard();
});
}
#override
Widget build(BuildContext context) {
return new Scaffold( //title_page
appBar: new AppBar(
title: new Text('Category 時'),
centerTitle: true,
),
body: Column( //content
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: Stack(
children: <Widget>[
Container(
child: FlipCard(
direction: FlipDirection.HORIZONTAL,
front: Material( //front_side
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25.0),
image: DecorationImage(
image: AssetImage(photos[photoIndex]), //images front
fit: BoxFit.cover)),
),
elevation: 20.0,
borderRadius: BorderRadius.all(Radius.circular(40.0)),
),
back: Material( //back_side
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25.0),
image: DecorationImage(
image: AssetImage(photos1[photoIndex]), // images back
fit: BoxFit.cover)),
),
elevation: 20.0,
borderRadius: BorderRadius.all(Radius.circular(40.0)),
),
),
height: 400.0,//flashcard
width: 370.0,
),
Positioned(
top: 380.0, //dots
left: 25.0,
right: 25.0,
child: SelectedPhoto(numberOfDots: photos.length,
photoIndex: photoIndex),
)
],
),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text('Prev', textScaleFactor: 1.3,),
onPressed: _previousImage,
elevation: 10.0,
color: Colors.redAccent,
textColor: Colors.white,
),
SizedBox(width: 10.0), //space
ButtonTheme( //bookmark
minWidth: 10,
height: 10,
child: RaisedButton(
child: Image.asset(bookmarkicon, scale: 3.5,),
color: Colors.transparent,
onPressed: (){
if(this.bookmarkicon=='assets/bookmarkoff.png'){
print(this.bookmarkicon +" is clicked");
setState(() {
bookmarkicon = bookmarkOn;
});
this.bookmarkicon = 'assets/bookmarkon.png';
}
else if(this.bookmarkicon=='assets/bookmarkon.png'){
print(this.bookmarkicon +" is clicked");
this.bookmarkicon = 'assets/bookmarkoff.png';
setState(() {
bookmarkicon = bookmarkOff;
});
}
//any implementation for bookmark will be here ;)
},
),
),
SizedBox(width: 10.0), //space
RaisedButton(
child: Text('Next', textScaleFactor: 1.3,),
onPressed:_nextImage,
elevation: 10.0,
color: Colors.redAccent,
textColor: Colors.white,
)
],
)
],
));
}
}
class SelectedPhoto extends StatelessWidget {
final int numberOfDots;
final int photoIndex;
SelectedPhoto({this.numberOfDots, this.photoIndex});
Widget _inactivePhoto() {
return new Container(
child: new Padding(
padding: const EdgeInsets.only(left: 3.0, right: 3.0),
child: Container(
height: 8.0,
width: 8.0,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(4.0)
),
),
)
);
}
Widget _activePhoto() {
return Container(
child: Padding(
padding: EdgeInsets.only(left: 3.0, right: 3.0),
child: Container(
height: 10.0,
width: 10.0,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(5.0),
boxShadow: [
BoxShadow(
color: Colors.grey,
spreadRadius: 0.0,
blurRadius: 2.0
)
]
),
),
),
);
}
List<Widget> _buildDots() {
List<Widget> dots = [];
for(int i = 0; i< numberOfDots; ++i) {
dots.add(
i == photoIndex ? _activePhoto(): _inactivePhoto()
);
}
return dots;
}
#override
Widget build(BuildContext context) {
return new Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _buildDots(),
),
);
}
}
The code works, but when the user pressed next, it will display the next card even though it is the back side showing.
So here's what I added to my code to make sure every time the user decides to see the next card, it shows the front side of the next card, not the back side:
First I defined a global variable:
GlobalKey<FlipCardState> cardKey = GlobalKey<FlipCardState>();
then it's passed to the key property of flip_card package:
FlipCard(
key: cardKey,
Then I use the variable to check the status of card whether the front of card is shown or the back, if the back side is shown then it will be toggled to show the front side of the next card when clicking on the "next card button":
onPressed: () {
if(cardKey.currentState != null) { //null safety
if (!cardKey.currentState!.isFront) {
cardKey.currentState!.toggleCard();
}
}
},
Adding to the onPressed property of the "next card" button.
Never mind, i just made it into one .dart and made my animator controller into global, and accessed it to .reverse() each time the next button is pressed.