我正在尝试实现类似火种的卡片刷卡效果(https://github.com/Ivaskuu/tinder_cards)(加上翻转效果,使双面可刷卡的外观更为美观),其结果如下:
“有效”滑动后,第二张卡出现了片刻,堆栈顶部的卡消失了,但随后整个堆栈又恢复了原始状态。 >
调试方面的观察:changeCardsOrder()似乎工作正常,因为调试时可以正确修改card []中的数据。也通过正确的card []构建栈(通过setState)。但是,屏幕会显示一秒钟后最初加载的堆栈。
以下是内部使用Flippable卡的可刷卡代码:
import 'dart:math';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:laconically/util/flippable_card.dart';
List<Alignment> cardsAlign = [ new Alignment(0.0, 1.0), new Alignment(0.0, 0.8), new Alignment(0.0, 0.0) ];
List<Size> cardsSize;
List<DocumentSnapshot> wordList;
class SwipableCard extends StatefulWidget
{
SwipableCard(BuildContext context, List<DocumentSnapshot> wl)
{
wordList = wl;
cardsSize = new List(wl.length);
for(int i = 0; i < wl.length; i++) {
cardsSize[i] = new Size(MediaQuery.of(context).size.width * (0.9 - i*0.05), MediaQuery.of(context).size.height * (0.6 - i*0.05));
}
// cardsSize[1] = new Size(MediaQuery.of(context).size.width * 0.85, MediaQuery.of(context).size.height * 0.55);
// cardsSize[2] = new Size(MediaQuery.of(context).size.width * 0.8, MediaQuery.of(context).size.height * 0.5);
}
@override
_SwipableCardState createState() => new _SwipableCardState();
}
class _SwipableCardState extends State<SwipableCard> with SingleTickerProviderStateMixin
{
int cardsCounter = 0;
List<FlippableCard> cards = new List();
AnimationController _controller;
final Alignment defaultFrontCardAlign = new Alignment(0.0, 0.0);
Alignment frontCardAlign;
double frontCardRot = 0.0;
@override
void initState()
{
super.initState();
// Init cards
for (cardsCounter = 0; cardsCounter < wordList.length; cardsCounter++)
{
cards.add(new FlippableCard(wordList[cardsCounter].data));
}
frontCardAlign = cardsAlign[2];
// Init the animation controller
_controller = new AnimationController(duration: new Duration(milliseconds: 700), vsync: this);
_controller.addListener(() => setState(() {}));
_controller.addStatusListener((AnimationStatus status)
{
if(status == AnimationStatus.completed) changeCardsOrder();
});
}
@override
Widget build(BuildContext context)
{
return Expanded
(
child: new Stack
(
children: <Widget>
[
backCard(),
middleCard(),
frontCard(),
// Prevent swiping if the cards are animating
_controller.status != AnimationStatus.forward ? new SizedBox.expand
(
child: new GestureDetector
(
// While dragging the first card
onPanUpdate: (DragUpdateDetails details)
{
// Add what the user swiped in the last frame to the alignment of the card
setState(()
{
// 20 is the "speed" at which moves the card
frontCardAlign = new Alignment
(
frontCardAlign.x + 20 * details.delta.dx / MediaQuery.of(context).size.width,
frontCardAlign.y + 40 * details.delta.dy / MediaQuery.of(context).size.height
);
frontCardRot = frontCardAlign.x; // * rotation speed;
});
},
// When releasing the first card
onPanEnd: (_)
{
// If the front card was swiped far enough to count as swiped
if(frontCardAlign.x > 3.0 || frontCardAlign.x < - 3.0)
{
animateCards();
}
else
{
// Return to the initial rotation and alignment
setState(()
{
frontCardAlign = defaultFrontCardAlign;
frontCardRot = 0.0;
});
}
},
)
) : new Container(),
],
)
);
}
Widget backCard()
{
return new Align
(
alignment: _controller.status == AnimationStatus.forward ? CardsAnimation.backCardAlignmentAnim(_controller).value : cardsAlign[0],
child: new SizedBox.fromSize
(
size: _controller.status == AnimationStatus.forward ? CardsAnimation.backCardSizeAnim(_controller).value : cardsSize[2],
child: cards[2]
),
);
}
Widget middleCard()
{
return new Align
(
alignment: _controller.status == AnimationStatus.forward ? CardsAnimation.middleCardAlignmentAnim(_controller).value : cardsAlign[1],
child: new SizedBox.fromSize
(
size: _controller.status == AnimationStatus.forward ? CardsAnimation.middleCardSizeAnim(_controller).value : cardsSize[1],
child: cards[1]
),
);
}
Widget frontCard()
{
return Align
(
alignment: _controller.status == AnimationStatus.forward ? CardsAnimation.frontCardDisappearAlignmentAnim(_controller, frontCardAlign).value : frontCardAlign,
child: new Transform.rotate
(
angle: (pi / 180.0) * frontCardRot,
child: new SizedBox.fromSize
(
size: cardsSize[0],
child: cards[0]
),
)
);
}
void changeCardsOrder()
{
setState(()
{
// Swap cards (back card becomes the middle card; middle card becomes the front card, front card becomes a new bottom card)
var temp = cards[0];
int i=1;
for(;i<wordList.length; i++) {
cards[i-1] = cards[i];
}
cards[i-1] = temp;
// cards[i-1] = new FlippableCard(wordList[cardsCounter - 1].data);
// cardsCounter++;
frontCardAlign = defaultFrontCardAlign;
frontCardRot = 0.0;
});
}
void animateCards()
{
_controller.stop();
_controller.value = 0.0;
_controller.forward();
}
}
class CardsAnimation
{
static Animation<Alignment> backCardAlignmentAnim(AnimationController parent)
{
return new AlignmentTween
(
begin: cardsAlign[0],
end: cardsAlign[1]
).animate
(
new CurvedAnimation
(
parent: parent,
curve: new Interval(0.4, 0.7, curve: Curves.easeIn)
)
);
}
static Animation<Size> backCardSizeAnim(AnimationController parent)
{
return new SizeTween
(
begin: cardsSize[2],
end: cardsSize[1]
).animate
(
new CurvedAnimation
(
parent: parent,
curve: new Interval(0.4, 0.7, curve: Curves.easeIn)
)
);
}
static Animation<Alignment> middleCardAlignmentAnim(AnimationController parent)
{
return new AlignmentTween
(
begin: cardsAlign[1],
end: cardsAlign[2]
).animate
(
new CurvedAnimation
(
parent: parent,
curve: new Interval(0.2, 0.5, curve: Curves.easeIn)
)
);
}
static Animation<Size> middleCardSizeAnim(AnimationController parent)
{
return new SizeTween
(
begin: cardsSize[1],
end: cardsSize[0]
).animate
(
new CurvedAnimation
(
parent: parent,
curve: new Interval(0.2, 0.5, curve: Curves.easeIn)
)
);
}
static Animation<Alignment> frontCardDisappearAlignmentAnim(AnimationController parent, Alignment beginAlign)
{
return new AlignmentTween
(
begin: beginAlign,
end: new Alignment(beginAlign.x > 0 ? beginAlign.x + 30.0 : beginAlign.x - 30.0, 0.0) // Has swiped to the left or right?
).animate
(
new CurvedAnimation
(
parent: parent,
curve: new Interval(0.0, 0.5, curve: Curves.easeIn)
)
);
}
}
以下是可翻转式卡的代码:
import 'package:flutter/material.dart';
class FlippableCard extends StatefulWidget {
Map<String, dynamic> wordMap;
@override
State<StatefulWidget> createState() => FlippableCardState(wordMap);
FlippableCard(Map<String, dynamic> wm) {
wordMap = wm;
}
}
class FlippableCardState extends State<FlippableCard>
with TickerProviderStateMixin {
Map<String, dynamic> wordMap;
FlippableCardState(Map<String, dynamic> wm) {
wordMap = wm;
}
AnimationController _controller;
Animation<double> _frontScale;
Animation<double> _backScale;
@override
void initState() {
// TODO: implement initState
super.initState();
_controller = new AnimationController(
vsync: this,
duration: const Duration(milliseconds: 800),
);
_frontScale = new Tween(
begin: 1.0,
end: 0.0,
).animate(new CurvedAnimation(
parent: _controller,
curve: new Interval(0.0, 0.5, curve: Curves.easeIn),
));
_backScale = new CurvedAnimation(
parent: _controller,
curve: new Interval(0.5, 1.0, curve: Curves.easeOut),
);
}
@override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
AnimatedBuilder(
child: GestureDetector(
child: Card(
elevation: 30.0,
margin: EdgeInsets.fromLTRB(10.0, 80.0, 10.0, 0.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: Padding(
padding: EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 30.0),
child: Text(
wordMap['word'],
textScaleFactor: 5.0,
style: TextStyle(
fontFamily: 'Cursive', color: Colors.green),
),
),
Padding(
padding: EdgeInsets.fromLTRB(0.0, 20.0, 20.0, 0.0),
child: Text(
wordMap['type'],
softWrap: true,
textAlign: TextAlign.center,
textScaleFactor: 0.9,
),
),
Padding(
padding: EdgeInsets.fromLTRB(0.0, 20.0, 20.0, 0.0),
child: Text(
wordMap['value'],
softWrap: true,
textAlign: TextAlign.center,
textScaleFactor: 1.1,
),
)
],
),
),
),
onTap: () {
setState(() {
if (_controller.isCompleted || _controller.velocity > 0)
_controller.reverse();
else
_controller.forward();
});
},
),
animation: _frontScale,
builder: (BuildContext context, Widget child) {
final Matrix4 transform = new Matrix4.identity()
..scale(1.0, _frontScale.value, 1.0);
return Transform(
transform: transform,
alignment: FractionalOffset.center,
child: child,
);
},
),
AnimatedBuilder(
child: GestureDetector(
child: Card(
elevation: 30.0,
margin: EdgeInsets.fromLTRB(10.0, 80.0, 10.0, 30.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: Padding(
padding: EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 30.0),
child: Text(
'Examples',
style: TextStyle(color: Colors.black45),
softWrap: true,
textAlign: TextAlign.left,
textScaleFactor: 2.0,
),
),
Padding(
padding: EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 30.0),
child: Text(
wordMap['examples'][0],
softWrap: true,
textAlign: TextAlign.left,
textScaleFactor: 1.1,
),
),
Padding(
padding: EdgeInsets.fromLTRB(20.0, 0.0, 20.0, 30.0),
child: Text(
wordMap['examples'][1],
softWrap: true,
textAlign: TextAlign.left,
textScaleFactor: 1.1,
),
)
],
),
)),
onTap: () {
setState(() {
if (_controller.isCompleted || _controller.velocity > 0)
_controller.reverse();
else
_controller.forward();
});
},
),
animation: _backScale,
builder: (BuildContext context, Widget child) {
final Matrix4 transform = new Matrix4.identity()
..scale(1.0, _backScale.value, 1.0);
return new Transform(
transform: transform,
alignment: FractionalOffset.center,
child: child,
);
},
)
],
);
}
}
这是实现可滑动刷卡的家用dart文件:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:laconically/util/custom_fab.dart';
import 'package:laconically/util/swipable_card.dart';
List<DocumentSnapshot> wordList;
class Home extends StatefulWidget {
@override
State<StatefulWidget> createState() => HomeState();
Home(List<DocumentSnapshot> wl) {
wordList = wl;
}
}
class HomeState extends State<Home> {
bool dragOverTarget = false;
int cardsCounter = 0;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: false,
backgroundColor: Colors.white,
body: Column(
children: <Widget>[
SwipableCard(context, wordList),
Padding(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 30.0, 40.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FancyFab(
onPressed: null,
),
],
),
),
],
),
);
}
}
您知道这种行为背后的原因是什么吗?