Flutter中Swipeable Card Stack无法加载API JSON卡片问题
Flutter swipeable_card_stack 无法加载API返回的多张卡片问题修复
问题现象
使用swipeable_card_stack插件展示用户资料卡片时,静态数据能正常显示多张卡片,但传入API返回的JSON列表仅能显示单张卡片。
问题根源分析
- UI未触发刷新:API请求完成后向
cardItems添加数据,但未调用setState()通知Flutter重建UI,导致初始空列表的UI状态一直保留。 - 重复创建CardView实例:
SwipeableCardsSection的items参数中,对已存在的cardItems列表再次调用map创建新的CardView实例,导致插件无法正确识别卡片栈结构。 - CardView状态管理错误:
_CardViewState通过构造函数保存数据,未使用widget对象的属性,后续数据更新无法同步到UI。 - 控制器状态未同步:在未刷新UI的情况下直接给
_cardController添加Item,插件内部状态未与数据同步。
修复方案
1. 触发UI刷新
在API请求成功获取数据后,调用setState()让Flutter重新构建UI。
2. 简化卡片列表传递
直接将cardItems列表传入SwipeableCardsSection的items参数,避免重复创建实例。
3. 修正CardView数据获取方式
_CardViewState直接使用widget.name和widget.profileImage获取数据,确保数据更新能同步到UI。
4. 同步控制器状态
在setState()后重新初始化控制器并添加卡片,保证插件内部状态与数据一致。
修改后的代码
Dashboard 组件
class Dashboard extends StatefulWidget { @override State<StatefulWidget> createState() { return DashboardStateHolder(); } } class DashboardStateHolder extends State<Dashboard> { int counter = 20; late QuadCardsResponse quadCardsResponseNew; late SharedPreferences sharedPreferences; late String? id = "", name = "", email = "", mobNum = "", colgName = "", colgShort = "", primaryColor = "", secondaryColor = "", accessToken = "", latitude = "", longitude = ""; int cardsLength = 0; List<CardView> cardItems = <CardView>[]; SwipeableCardSectionController _cardController = SwipeableCardSectionController(); @override void initState() { super.initState(); getRequiredData(); } Future<void> getRequiredData() async { sharedPreferences = await SharedPreferences.getInstance(); // 省略原有SharedPreferences读取逻辑 await getSwipeCardsWS(); } Future<void> getSwipeCardsWS() async { DesignConstants.showLoaderDialog(context, "Hold on ... !"); SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); Response response; final Dio dio = Dio(); dio.options.headers['content-Type'] = 'application/json'; dio.options.headers["Authorization"] = "${sharedPreferences.getString('api_access_token')}"; response = await dio.post(apiConfig.getSwipeCards, queryParameters: { 'latitude': sharedPreferences.getString("lat"), 'longitude': sharedPreferences.getString("long"), }); print(response.data.toString()); if (response.data['status'] == "success") { print("cards-length"); print(response.data['data'].length); cardsLength = response.data['data'].length; Navigator.pop(context); quadCardsResponseNew = quadCardsResponseFromJson(response.toString()); print("data-coming-here-now"); print(quadCardsResponseNew.data.toString()); cardItems.clear(); quadCardsResponseNew.data.forEach((element) { cardItems.add(CardView(element.name, element.profileImages[0])); }); print("_________LENGTH_________"); print(cardItems.length); print(cardItems.toString()); setState(() {}); _cardController = SwipeableCardSectionController(); for (var card in cardItems) { _cardController.addItem(card); } } else { Navigator.pop(context); print('Error in API response'); } } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => QuadHome())); return Future.value(false); }, child: Scaffold( backgroundColor: ColorConstant.otherWhiteShade, floatingActionButton: Padding( padding: EdgeInsets.fromLTRB(0, 0, 0, 30), child: MaterialButton( child: Padding( padding: EdgeInsets.all(20), child: Text( "quad", style: TextStyle( fontSize: 18.0, fontFamily: 'comfortaa', fontWeight: FontWeight.bold, letterSpacing: 00, color: ColorConstant.quadTextSwipeCard, ), ), ), onPressed: () { setState(() {}); }, color: Colors.white, textColor: Colors.deepPurpleAccent, padding: EdgeInsets.all(16), shape: CircleBorder( side: BorderSide( color: ColorConstant.quadFloatingButtonColorShoadow .withOpacity(0.3), width: 3, style: BorderStyle.solid)), )), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, body: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Center( child: Padding( padding: EdgeInsets.fromLTRB(0, 20, 0, 0), child: Image.asset( 'images/quadheadbar.png', height: 100, width: 250, ), ), ), SwipeableCardsSection( cardController: _cardController, context: context, items: cardItems, onCardSwiped: (dir, index, widget) {}, enableSwipeUp: true, enableSwipeDown: true, ), Container( margin: const EdgeInsets.symmetric(vertical: 20.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( padding: const EdgeInsets.fromLTRB(30, 0, 0, 0), child: MaterialButton( onPressed: () {}, color: ColorConstant.cancelOptionSwipeCard, textColor: Colors.white, child: Icon( CupertinoIcons.clear, size: 48, ), padding: EdgeInsets.all(16), shape: CircleBorder(), )), Padding( padding: const EdgeInsets.fromLTRB(0, 0, 30, 0), child: MaterialButton( onPressed: () {}, color: ColorConstant.likeOptionSwipeCard, textColor: Colors.white, child: Icon( CupertinoIcons.heart_fill, size: 48, ), padding: EdgeInsets.all(16), shape: CircleBorder(), )) ], ), ), ], ), ), ); } }
CardView 组件
class CardView extends StatefulWidget { final String name; final String profileImage; const CardView(this.name, this.profileImage, {super.key}); @override State<CardView> createState() => _CardViewState(); } class _CardViewState extends State<CardView> { TextEditingController graduationYearController = TextEditingController(); String _chosenValue = ''; int selectedValue = 0; late List<dynamic> streamList = []; List<String> streamData = <String>[]; late QuadCardsResponse quadCardsResponse; @override void initState() { super.initState(); print("profile-image"); print(widget.profileImage); } @override Widget build(BuildContext context) { return Card( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(50), ), child: Stack( children: <Widget>[ widget.profileImage.isNotEmpty ? Container( foregroundDecoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(50)), image: DecorationImage( image: NetworkImage(widget.profileImage), fit: BoxFit.cover), ), ) : Container( height: 200, child: Image.asset( 'images/quad_placeholder.png', ), ), Align( alignment: Alignment.bottomCenter, child: Container( height: 250, width: double.infinity, foregroundDecoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(50)), gradient: LinearGradient( end: const Alignment(0.0, -1), begin: const Alignment(0.0, 0.4), colors: <Color>[ const Color(0x8A000000), ColorConstant.quadTextSwipeCard.withOpacity(0.0) ], ), ), ), ), Align( alignment: Alignment.topCenter, child: Container( padding: const EdgeInsets.symmetric( vertical: 16.0, horizontal: 16.0), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: const <Widget>[ Text( "SDSU", textAlign: TextAlign.start, style: TextStyle( color: Colors.white, fontFamily: 'comfortaa', fontWeight: FontWeight.w900, letterSpacing: 2), ), ], )), ), Row( children: const [ Align( alignment: Alignment.bottomLeft, child: Padding( padding: EdgeInsets.fromLTRB(20, 00, 00, 60), child: Chip( backgroundColor: ColorConstant.chipColor, label: Text('Business', style: TextStyle( color: ColorConstant.otherWhiteShade, fontSize: 14.0, fontFamily: 'comfortaa'), ), ), ), ), Align( alignment: Alignment.bottomLeft, child: Padding( padding: EdgeInsets.fromLTRB(20, 00, 00, 60), child: Chip( backgroundColor: ColorConstant.chipColor, label: Text('2024', style: TextStyle( color: ColorConstant.otherWhiteShade, fontSize: 14.0, fontFamily: 'comfortaa') ), ), ), ), ], ), Align( alignment: Alignment.bottomLeft, child: Container( padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), child: Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Padding( padding: const EdgeInsets.fromLTRB(00, 00, 00, 100), child: Text( widget.name, style: TextStyle( color: Colors.white, fontSize: 40.0, fontFamily: 'comfortaa', fontWeight: FontWeight.w900), ), ), ], ), ), ), ], ), ); } }
内容的提问来源于stack exchange,提问作者Robert S
相关产品推荐
相关产品推荐

