Flutter中选中索引时如何动态更新Wrap子组件的边框样式?
问题:Flutter中无法动态更新Wrap组件内容器的边框样式
我写了一个生成Widget列表并存入变量,再作为Wrap子元素的函数。现在的问题是,想根据选中的索引动态更新对应容器的边框样式,但边框始终停留在列表生成时的初始选中状态,没法随索引变化更新。
现有代码
变量与生成函数
int selectedFeature = 0; List<Widget> features = <Widget>[]; void generateFeatured() { var featured = <Widget>[]; if (movies.length >= 5) { for (var i = 0; i < 5; i++) { featured.add( Container( height: 150, width: 220, decoration: BoxDecoration( border: selectedFeature == i ? Border.all(color: const Color(0xff39a936), width: 2) : Border.all(color: Colors.transparent), borderRadius: BorderRadius.circular(25), image: DecorationImage( fit: BoxFit.cover, alignment: Alignment.topCenter, image: NetworkImage( movies[i]["cover_image"].toString(), ), ), ), child: Padding( padding: const EdgeInsets.only( left: 20, right: 20, bottom: 15, top: 15, ), child: Align( alignment: Alignment.bottomLeft, child: Container( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), child: Text( movies[i]["title"].toString().toUpperCase(), style: const TextStyle( fontFamily: "MADE", fontWeight: FontWeight.bold, ), ), ).asGlass( frosted: false, clipBorderRadius: BorderRadius.circular(4), tintColor: const Color(0xff41535f), ), ), ), ), ); } } else { for (var i = 0; i < movies.length; i++) { featured.add( Container( height: 150, width: 220, decoration: BoxDecoration( border: selectedFeature == i ? Border.all(color: const Color(0xff39a936), width: 2) : Border.all(color: Colors.transparent), borderRadius: BorderRadius.circular(25), image: DecorationImage( fit: BoxFit.cover, alignment: Alignment.topCenter, image: NetworkImage( movies[i]["cover_image"].toString(), ), ), ), child: Padding( padding: const EdgeInsets.only( left: 20, right: 20, bottom: 15, top: 15, ), child: Align( alignment: Alignment.bottomLeft, child: Container( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), child: Text( movies[i]["title"].toString().toUpperCase(), style: const TextStyle( fontFamily: "MADE", fontWeight: FontWeight.bold, ), ), ).asGlass( frosted: false, clipBorderRadius: BorderRadius.circular(4), tintColor: const Color(0xff41535f), ), ), ), ), ); } } setState(() { features = featured; }); }
Wrap组件使用
Wrap( alignment: WrapAlignment.start, spacing: 15, runSpacing: 15, children: features, ),
问题表现
边框始终保持列表生成时的初始选中状态,无法随selectedFeature的变化更新,错误的图片带有边框:
解决方法
问题根源是你在generateFeatured函数中创建Container时,直接把当时的selectedFeature == i的结果固化到了Widget中,后续selectedFeature变化时,这些已经生成的Widget不会重新计算边框样式。
方式1:直接在build里生成列表(最简单高效)
把生成列表的逻辑移到build方法中,这样每次setState触发重建时,都会重新计算每个容器的边框样式:
@override Widget build(BuildContext context) { return Wrap( alignment: WrapAlignment.start, spacing: 15, runSpacing: 15, children: _buildFeaturedItems(), ); } List<Widget> _buildFeaturedItems() { var featured = <Widget>[]; int count = movies.length >=5 ? 5 : movies.length; for (var i = 0; i < count; i++) { featured.add( GestureDetector( onTap: () { setState(() { selectedFeature = i; }); }, child: Container( height: 150, width: 220, decoration: BoxDecoration( border: selectedFeature == i ? Border.all(color: const Color(0xff39a936), width: 2) : Border.all(color: Colors.transparent), borderRadius: BorderRadius.circular(25), image: DecorationImage( fit: BoxFit.cover, alignment: Alignment.topCenter, image: NetworkImage(movies[i]["cover_image"].toString()), ), ), child: Padding( padding: const EdgeInsets.only(left:20, right:20, bottom:15, top:15), child: Align( alignment: Alignment.bottomLeft, child: Container( padding: const EdgeInsets.symmetric(horizontal:4, vertical:2), child: Text( movies[i]["title"].toString().toUpperCase(), style: const TextStyle(fontFamily: "MADE", fontWeight: FontWeight.bold), ), ).asGlass( frosted: false, clipBorderRadius: BorderRadius.circular(4), tintColor: const Color(0xff41535f), ), ), ), ), ), ); } return featured; }
方式2:封装独立的StatefulWidget(适合复杂子项)
如果子项逻辑复杂,不想每次重建所有子Widget,可以把电影卡片封装成独立的StatefulWidget,让它根据传入的选中状态自动更新样式:
class MovieCard extends StatefulWidget { final Map movie; final int index; final int selectedIndex; final VoidCallback onTap; const MovieCard({ super.key, required this.movie, required this.index, required this.selectedIndex, required this.onTap, }); @override State<MovieCard> createState() => _MovieCardState(); } class _MovieCardState extends State<MovieCard> { @override Widget build(BuildContext context) { return GestureDetector( onTap: widget.onTap, child: Container( height: 150, width: 220, decoration: BoxDecoration( border: widget.selectedIndex == widget.index ? Border.all(color: const Color(0xff39a936), width: 2) : Border.all(color: Colors.transparent), borderRadius: BorderRadius.circular(25), image: DecorationImage( fit: BoxFit.cover, alignment: Alignment.topCenter, image: NetworkImage(widget.movie["cover_image"].toString()), ), ), child: Padding( padding: const EdgeInsets.only(left:20, right:20, bottom:15, top:15), child: Align( alignment: Alignment.bottomLeft, child: Container( padding: const EdgeInsets.symmetric(horizontal:4, vertical:2), child: Text( widget.movie["title"].toString().toUpperCase(), style: const TextStyle(fontFamily: "MADE", fontWeight: FontWeight.bold), ), ).asGlass( frosted: false, clipBorderRadius: BorderRadius.circular(4), tintColor: const Color(0xff41535f), ), ), ), ), ); } }
父组件中使用:
Wrap( alignment: WrapAlignment.start, spacing: 15, runSpacing: 15, children: List.generate( movies.length >=5 ?5 : movies.length, (i) => MovieCard( movie: movies[i], index: i, selectedIndex: selectedFeature, onTap: () { setState(() { selectedFeature = i; }); }, ), ), )
内容的提问来源于stack exchange,提问作者Alexander Roumeliotis
相关产品推荐
相关产品推荐

