我收到了一个 RangeError
异常,我不确定为什么会发生这种情况,首先是堆栈跟踪:
======== Exception caught by widgets library =======================================================
The following RangeError was thrown building ActivitiesCard(dirty, dependencies: [_EffectiveTickerMode, MediaQuery], state: _ActivitiesCardState#c7ad9(tickers: tracking 1 ticker)):
RangeError (index): Invalid value: Not in inclusive range 0..1: 2
The relevant error-causing widget was:
ActivitiesCard file:///C:/Users/BinsNet/Documents/Activities/activitiesinyourarea/lib/ui/SwipeScreen/SwipeScreen.dart:248:28
When the exception was thrown, this was the stack:
#0 List.[] (dart:core-patch/growable_array.dart:254:60)
#1 _SwipeScreenState._asyncCards.<anonymous closure> (package:activities/ui/SwipeScreen/SwipeScreen.dart:272:38)
#2 _ActivitiesCardState._buildCard (package:activities/ui/ActivitiesCard.dart:158:35)
#3 _ActivitiesCardState._buildCards (package:activities/ui/ActivitiesCard.dart:166:17)
#4 _ActivitiesCardState.build (package:activities/ui/ActivitiesCard.dart:269:29)
在第 271 行的滑动屏幕上,我想将 cardBuilder
与索引一起使用,但我不知道索引是如何初始化的。我有代码示例中的这一行,所以我不知道它是如何工作的:
cardBuilder: (context, index) =>
_buildCard(data[index]),
这是我的 2 个类的完整代码,也许有人可以帮助我:
import 'package:activities/model/Activities.dart';
import 'package:activities/ui/ActivitiesCard.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:activities/constants.dart';
import 'package:activities/model/User.dart';
import 'package:activities/services/FirebaseHelper.dart';
import 'package:activities/services/helper.dart';
import 'package:activities/ui/matchScreen/MatchScreen.dart';
import 'package:activities/ui/userDetailsScreen/ActivitiesDetailsScreen.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class SwipeScreen extends StatefulWidget {
final User user;
const SwipeScreen({Key key, this.user}) : super(key: key);
@override
_SwipeScreenState createState() => _SwipeScreenState(user);
}
class _SwipeScreenState extends State<SwipeScreen> {
final User user;
FireStoreUtils _fireStoreUtils = FireStoreUtils();
//Stream<List<User>> tinderUsers;
Stream<List<Activities>> streamActivities;
List<Activities> swipedActivities = [];
List<Activities> activities = [];
List<User> swipedUsers = [];
List<User> users = [];
CardController controller;
_SwipeScreenState(this.user);
@override
void initState() {
super.initState();
_setupActivities();
}
@override
void dispose() {
_fireStoreUtils.closeActivitiesStream();
super.dispose();
}
@override
Widget build(BuildContext context) {
return StreamBuilder<List<Activities>>(
stream: streamActivities,
initialData: [],
builder: (context, snapshot) {
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Color(COLOR_ACCENT)),
),
);
case ConnectionState.active:
return _asyncCards(context, snapshot.data);
case ConnectionState.done:
}
return null; // unreachable
},
);
}
Widget _buildCard(Activities streamActivities) {
return GestureDetector(
onTap: () async {
_launchDetailsScreen(streamActivities);
},
child: Card(
child: Stack(
children: <Widget>[
Positioned(
right: 5,
child: IconButton(
icon: Icon(
Icons.keyboard_arrow_down,
color: Colors.white,
),
iconSize: 30,
//ToDo onPressed: () => _onCardSettingsClick(activities[index]),
),
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
verticalDirection: VerticalDirection.down,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
streamActivities.activity,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
),
Row(
children: <Widget>[
Icon(
Icons.school,
color: Colors.white,
),
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
streamActivities.description,
style: TextStyle(
color: Colors.white,
),
),
),
],
),
Row(
children: <Widget>[
Icon(
Icons.location_on,
color: Colors.white,
),
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
streamActivities.locationName,
style: TextStyle(
color: Colors.white,
),
),
),
],
),
],
),
)
],
),
shape: RoundedRectangleBorder(
side: BorderSide.none,
borderRadius: BorderRadius.circular(25),
),
color: Color(COLOR_PRIMARY),
),
);
}
Future<void> _launchDetailsScreen(Activities streamActivities) async {
CardSwipeOrientation result =
await Navigator.of(context).push(new MaterialPageRoute(
builder: (context) =>
ActivitiesDetailsScreen(
activities: streamActivities,
isMatch: false
)));
if (result != null) {
if (result == CardSwipeOrientation.LEFT) {
controller.triggerLeft();
} else {
controller.triggerRight();
}
}
}
_onCardSettingsClick(Activities activity) {
final action = CupertinoActionSheet(
message: Text(
activity.activity,
style: TextStyle(fontSize: 15.0),
),
actions: <Widget>[
CupertinoActionSheetAction(
child: Text("Block user"),
onPressed: () async {
},
),
CupertinoActionSheetAction(
child: Text("Report user"),
onPressed: () async {
},
),
],
cancelButton: CupertinoActionSheetAction(
child: Text(
"Cancel",
),
onPressed: () {
Navigator.pop(context);
},
),
);
showCupertinoModalPopup(context: context, builder: (context) => action);
}
Widget _asyncCards(BuildContext context, List<Activities> data) {
activities = data;
debugPrint('Data length' + data.length.toString());
if (data == null || data.isEmpty)
return Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'No Activities arround you. Increase the distance radius to get more recommendations.',
textAlign: TextAlign.center,
),
),
);
return Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Flexible(
child: Stack(children: [
Container(
child: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'No Activities arround you. Increase '
'the distance radius to get more recommendations.',
textAlign: TextAlign.center,
),
),
),
),
Container(
height: MediaQuery
.of(context)
.size
.height * 0.9,
width: MediaQuery
.of(context)
.size
.width,
child: new ActivitiesCard(
animDuration: 500,
orientation: AmassOrientation.BOTTOM,
totalNum: data.length ,
stackNum: 3,
swipeEdge: 15,
maxWidth: MediaQuery
.of(context)
.size
.width,
maxHeight: MediaQuery
.of(context)
.size
.height,
minWidth: MediaQuery
.of(context)
.size
.width * 0.9,
minHeight: MediaQuery
.of(context)
.size
.height * 0.9,
cardBuilder: (context, index) =>
_buildCard(data[index]),
cardController: controller = CardController(),
swipeCompleteCallback:
(CardSwipeOrientation orientation, int index) async {
if (orientation == CardSwipeOrientation.LEFT ||
orientation == CardSwipeOrientation.RIGHT) {
debugPrint('Index ' + index.toString());
await _fireStoreUtils.incrementSwipe();
swipedActivities.add(data[index]);
data.removeAt(index);
}
else if (orientation == CardSwipeOrientation.LEFT) {
debugPrint('Index 1 ' + index.toString());
swipedActivities.add(data[index]);
data.removeAt(index);
}
}
),
),
]),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
FloatingActionButton(
elevation: 1,
heroTag: 'left',
onPressed: () {
controller.triggerLeft();
},
backgroundColor: Colors.white,
mini: false,
child: Icon(
Icons.close,
color: Colors.red,
size: 40,
),
),
FloatingActionButton(
elevation: 1,
heroTag: 'right',
onPressed: () {
controller.triggerRight();
},
backgroundColor: Colors.white,
mini: false,
child: Icon(
Icons.attach_email,
color: Colors.black,
size: 40,
),
)
],
),
)
]);
}
_setupActivities() async {
streamActivities = _fireStoreUtils.getActivities();
debugPrint(_fireStoreUtils.getActivities().toString());
}
}
import 'package:flutter/material.dart';
List<Size> _cardSizes = new List();
List<Alignment> _cardAligns = new List();
/// A Tinder-Like Widget.
class ActivitiesCard extends StatefulWidget {
CardBuilder _cardBuilder;
int _totalNum;
int _stackNum;
int _animDuration;
double _swipeEdge;
double _swipeEdgeVertical;
bool _swipeUp;
bool _swipeDown;
bool _allowVerticalMovement;
CardSwipeCompleteCallback swipeCompleteCallback;
CardDragUpdateCallback swipeUpdateCallback;
CardController cardController;
// double _maxWidth;
// double _minWidth;
// double _maxHeight;
// double _minHeight;
@override
_ActivitiesCardState createState() => _ActivitiesCardState();
/// Constructor requires Card Widget Builder [cardBuilder] & your card count [totalNum]
/// , option includes: stack orientation [orientation], number of card display in same time [stackNum]
/// , [swipeEdge] is the edge to determine action(recover or swipe) when you release your swiping card
/// it is the value of alignment, 0.0 means middle, so it need bigger than zero.
/// , and size control params;
ActivitiesCard(
{@required CardBuilder cardBuilder,
@required int totalNum,
AmassOrientation orientation = AmassOrientation.BOTTOM,
int stackNum = 3,
int animDuration = 800,
double swipeEdge = 3.0,
double swipeEdgeVertical = 8.0,
bool swipeUp = false,
bool swipeDown = false,
double maxWidth,
double maxHeight,
double minWidth,
double minHeight,
bool allowVerticalMovement = true,
this.cardController,
this.swipeCompleteCallback,
this.swipeUpdateCallback})
: this._cardBuilder = cardBuilder,
this._totalNum = totalNum,
assert(stackNum > 1),
this._stackNum = stackNum,
this._animDuration = animDuration,
assert(swipeEdge > 0),
this._swipeEdge = swipeEdge,
assert(swipeEdgeVertical > 0),
this._swipeEdgeVertical = swipeEdgeVertical,
this._swipeUp = swipeUp,
this._swipeDown = swipeDown,
assert(maxWidth > minWidth && maxHeight > minHeight),
this._allowVerticalMovement = allowVerticalMovement
// this._maxWidth = maxWidth,
// this._minWidth = minWidth,
// this._maxHeight = maxHeight,
// this._minHeight = minHeight
{
double widthGap = maxWidth - minWidth;
double heightGap = maxHeight - minHeight;
_cardAligns = new List();
_cardSizes = new List();
for (int i = 0; i < _stackNum; i++) {
_cardSizes.add(new Size(minWidth + (widthGap / _stackNum) * i,
minHeight + (heightGap / _stackNum) * i));
switch (orientation) {
case AmassOrientation.BOTTOM:
_cardAligns.add(
new Alignment(0.0, (0.5 / (_stackNum - 1)) * (stackNum - i)));
break;
case AmassOrientation.TOP:
_cardAligns.add(
new Alignment(0.0, (-0.5 / (_stackNum - 1)) * (stackNum - i)));
break;
case AmassOrientation.LEFT:
_cardAligns.add(
new Alignment((-0.5 / (_stackNum - 1)) * (stackNum - i), 0.0));
break;
case AmassOrientation.RIGHT:
_cardAligns.add(
new Alignment((0.5 / (_stackNum - 1)) * (stackNum - i), 0.0));
break;
}
}
}
}
class _ActivitiesCardState extends State<ActivitiesCard>
with TickerProviderStateMixin {
Alignment frontCardAlign;
AnimationController _animationController;
int _currentFront;
static int _trigger; // 0: no trigger; -1: trigger left; 1: trigger right
Widget _buildCard(BuildContext context, int realIndex) {
if (realIndex < 0) {
return Container();
}
int index = realIndex - _currentFront;
if (index == widget._stackNum - 1) {
return Align(
alignment: _animationController.status == AnimationStatus.forward
? frontCardAlign = CardAnimation.frontCardAlign(
_animationController,
frontCardAlign,
_cardAligns[widget._stackNum - 1],
widget._swipeEdge,
widget._swipeUp,
widget._swipeDown)
.value
: frontCardAlign,
child: Transform.rotate(
angle: (pi / 180.0) *
(_animationController.status == AnimationStatus.forward
? CardAnimation.frontCardRota(
_animationController, frontCardAlign.x)
.value
: frontCardAlign.x),
child: new SizedBox.fromSize(
size: _cardSizes[index],
child: widget._cardBuilder(
context, widget._totalNum - realIndex - 1),
)),
);
}
return Align(
alignment: _animationController.status == AnimationStatus.forward &&
(frontCardAlign.x > 3.0 || frontCardAlign.x < -3.0)
? CardAnimation.backCardAlign(_animationController,
_cardAligns[index], _cardAligns[index + 1])
.value
: _cardAligns[index],
child: new SizedBox.fromSize(
size: _animationController.status == AnimationStatus.forward &&
(frontCardAlign.x > 3.0 || frontCardAlign.x < -3.0)
? CardAnimation.backCardSize(_animationController,
_cardSizes[index], _cardSizes[index + 1])
.value
: _cardSizes[index],
child: widget._cardBuilder(context, widget._totalNum - realIndex - 1),
),
);
}
List<Widget> _buildCards(BuildContext context) {
List<Widget> cards = new List();
for (int i = _currentFront; i < _currentFront + widget._stackNum; i++) {
cards.add(_buildCard(context, i));
}
cards.add(new SizedBox.expand(
child: new GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
setState(() {
if (widget._allowVerticalMovement == true) {
frontCardAlign = new Alignment(
frontCardAlign.x +
details.delta.dx * 20 / MediaQuery.of(context).size.width,
frontCardAlign.y +
details.delta.dy *
30 /
MediaQuery.of(context).size.height);
} else {
frontCardAlign = new Alignment(
frontCardAlign.x +
details.delta.dx * 20 / MediaQuery.of(context).size.width,
0);
if (widget.swipeUpdateCallback != null) {
widget.swipeUpdateCallback(details, frontCardAlign);
}
}
if (widget.swipeUpdateCallback != null) {
widget.swipeUpdateCallback(details, frontCardAlign);
}
});
},
onPanEnd: (DragEndDetails details) {
animateCards(0);
},
),
));
return cards;
}
animateCards(int trigger) {
if (_animationController.isAnimating ||
_currentFront + widget._stackNum == 0) {
return;
}
_trigger = trigger;
_animationController.stop();
_animationController.value = 0.0;
_animationController.forward();
}
void triggerSwap(int trigger) {
animateCards(trigger);
}
// support for asynchronous data events
@override
void didUpdateWidget(covariant ActivitiesCard oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget._totalNum != oldWidget._totalNum) {
_initState();
}
}
@override
void initState() {
super.initState();
_initState();
}
void _initState() {
_currentFront = widget._totalNum - widget._stackNum;
frontCardAlign = _cardAligns[_cardAligns.length - 1];
_animationController = new AnimationController(
vsync: this, duration: Duration(milliseconds: widget._animDuration));
_animationController.addListener(() => setState(() {}));
_animationController.addStatusListener((AnimationStatus status) {
int index = widget._totalNum - widget._stackNum - _currentFront;
if (status == AnimationStatus.completed) {
CardSwipeOrientation orientation;
if (frontCardAlign.x < -widget._swipeEdge)
orientation = CardSwipeOrientation.LEFT;
else if (frontCardAlign.x > widget._swipeEdge)
orientation = CardSwipeOrientation.RIGHT;
else if (frontCardAlign.y < -widget._swipeEdgeVertical)
orientation = CardSwipeOrientation.UP;
else if (frontCardAlign.y > widget._swipeEdgeVertical)
orientation = CardSwipeOrientation.DOWN;
else {
frontCardAlign = _cardAligns[widget._stackNum - 1];
orientation = CardSwipeOrientation.RECOVER;
}
if (widget.swipeCompleteCallback != null)
widget.swipeCompleteCallback(orientation, index);
if (orientation != CardSwipeOrientation.RECOVER) changeCardOrder();
}
});
}
@override
Widget build(BuildContext context) {
widget.cardController?.addListener((trigger) => triggerSwap(trigger));
return Stack(children: _buildCards(context));
}
@override
dispose() {
this._animationController.dispose();
super.dispose();
}
changeCardOrder() {
setState(() {
_currentFront--;
frontCardAlign = _cardAligns[widget._stackNum - 1];
});
}
}
typedef Widget CardBuilder(BuildContext context, int index);
enum CardSwipeOrientation { LEFT, RIGHT, RECOVER, UP, DOWN }
/// swipe card to [CardSwipeOrientation.LEFT] or [CardSwipeOrientation.RIGHT]
/// , [CardSwipeOrientation.RECOVER] means back to start.
typedef CardSwipeCompleteCallback = void Function(
CardSwipeOrientation orientation, int index);
/// [DragUpdateDetails] of swiping card.
typedef CardDragUpdateCallback = void Function(
DragUpdateDetails details, Alignment align);
enum AmassOrientation { TOP, BOTTOM, LEFT, RIGHT }
class CardAnimation {
static Animation<Alignment> frontCardAlign(
AnimationController controller,
Alignment beginAlign,
Alignment baseAlign,
double swipeEdge,
bool swipeUp,
bool swipeDown) {
double endX, endY;
if (_ActivitiesCardState._trigger == 0) {
endX = beginAlign.x > 0
? (beginAlign.x > swipeEdge ? beginAlign.x + 10.0 : baseAlign.x)
: (beginAlign.x < -swipeEdge ? beginAlign.x - 10.0 : baseAlign.x);
endY = beginAlign.x > 3.0 || beginAlign.x < -swipeEdge
? beginAlign.y
: baseAlign.y;
if (swipeUp || swipeDown) {
if (beginAlign.y < 0) {
if (swipeUp)
endY =
beginAlign.y < -swipeEdge ? beginAlign.y - 10.0 : baseAlign.y;
} else if (beginAlign.y > 0) {
if (swipeDown)
endY = beginAlign.y > swipeEdge ? beginAlign.y + 10.0 : baseAlign.y;
}
}
} else if (_ActivitiesCardState._trigger == -1) {
endX = beginAlign.x - swipeEdge;
endY = beginAlign.y + 0.5;
} else {
endX = beginAlign.x + swipeEdge;
endY = beginAlign.y + 0.5;
}
return new AlignmentTween(begin: beginAlign, end: new Alignment(endX, endY))
.animate(
new CurvedAnimation(parent: controller, curve: Curves.easeOut));
}
static Animation<double> frontCardRota(
AnimationController controller, double beginRot) {
return new Tween(begin: beginRot, end: 0.0).animate(
new CurvedAnimation(parent: controller, curve: Curves.easeOut));
}
static Animation<Size> backCardSize(
AnimationController controller, Size beginSize, Size endSize) {
return new SizeTween(begin: beginSize, end: endSize).animate(
new CurvedAnimation(parent: controller, curve: Curves.easeOut));
}
static Animation<Alignment> backCardAlign(AnimationController controller,
Alignment beginAlign, Alignment endAlign) {
return new AlignmentTween(begin: beginAlign, end: endAlign).animate(
new CurvedAnimation(parent: controller, curve: Curves.easeOut));
}
}
typedef TriggerListener = void Function(int trigger);
class CardController {
TriggerListener _listener;
void triggerLeft() {
if (_listener != null) {
_listener(-1);
}
}
void triggerRight() {
if (_listener != null) {
_listener(1);
}
}
void addListener(listener) {
_listener = listener;
}
void removeListener() {
_listener = null;
}
}