Flutter Drawer导航异常:点击子项始终跳转至最后一个子项
问题分析与解决方案
嘿,我一眼就看出你遇到的问题啦!这个bug其实是Flutter里很常见的闭包陷阱导致的,我来给你拆解清楚并修复它。
问题根源
你在_buildServicesTiles方法的for循环里,用了一个外部声明的serviceName变量来遍历所有Service实例。但ListTile的onTap是一个闭包,它捕获的不是每次循环迭代时的具体Service对象,而是serviceName这个变量的引用。当循环全部执行完毕后,serviceName会指向最后一个Service实例,所以不管你点击哪个子项,最终都会用这个“最后值”来跳转——这就是为啥所有点击都跳到对应ExpansionTile的最后一个子项的原因!
两种修复方法(任选其一即可)
方法1:在循环内部创建局部变量
每次循环时,把当前迭代的Service实例赋值给一个局部变量,让闭包捕获这个局部变量的引用,就能避免引用被覆盖:
List<Widget> _buildServicesTiles(context, PrayerBook prayerBook) { List<Widget> servicesList = []; for (var serviceName in prayerBook.services) { // 新增局部变量,保存当前循环的Service实例 var currentService = serviceName; servicesList.add(new ListTile( title: new Text(currentService.title ?? 'No title'), onTap: () { Navigator.pop(context); Navigator.push(context, new MaterialPageRoute( builder: (BuildContext context) { return ServiceView( currentService: currentService, ); }, )); }, )); } return servicesList; }
方法2:使用forEach循环替代for-in循环
forEach的参数会在每次迭代时创建一个新的变量,闭包会自动捕获每次迭代的Service实例,从根源上避免引用问题:
List<Widget> _buildServicesTiles(context, PrayerBook prayerBook) { List<Widget> servicesList = []; // 用forEach遍历,参数会自动绑定当前迭代的实例 prayerBook.services.forEach((serviceName) { servicesList.add(new ListTile( title: new Text(serviceName.title ?? 'No title'), onTap: () { Navigator.pop(context); Navigator.push(context, new MaterialPageRoute( builder: (BuildContext context) { return ServiceView( currentService: serviceName, ); }, )); }, )); }); return servicesList; }
完整修正后的代码(以方法2为例)
import 'package:flutter/material.dart'; void main() async { runApp(new MyApp()); } class MyApp extends StatelessWidget { final data = buildData(); @override Widget build(BuildContext context) { return new MaterialApp( title: 'App', theme: new ThemeData( primarySwatch: Colors.green, ), home: ServiceView(currentService: data.prayerBooks[0].services[0]), ); } } class ServiceView extends StatelessWidget { final currentService; ServiceView({Key key, @required this.currentService}) : super(key: key); @override Widget build(BuildContext context) { final allPrayerBooks = buildData(); return new Scaffold( drawer: _buildDrawer(allPrayerBooks.prayerBooks), appBar: new AppBar( title: new Text(currentService.title), actions: <Widget>[], ), ); } Drawer _buildDrawer(prayerBooks) { return Drawer( child: new ListView.builder( itemBuilder: (BuildContext context, int index) => new DrawerPrayerBookEntry(context, prayerBooks[index]), itemCount: prayerBooks.length, ), ); } } class DrawerPrayerBookEntry extends StatelessWidget { const DrawerPrayerBookEntry(BuildContext context, this.prayerBook); final PrayerBook prayerBook; Widget _buildTiles(BuildContext context, PrayerBook prayerbook) { if (prayerbook.services.isEmpty) return new ListTile(title: new Text(prayerbook.title ?? 'No Title')); return new ExpansionTile( key: new PageStorageKey<PrayerBook>(prayerbook), title: new Text(prayerbook.title ?? 'No title'), children: _buildServicesTiles(context, prayerbook), ); } @override Widget build(BuildContext context) { return _buildTiles(context, PrayerBook); } List<Widget> _buildServicesTiles(context, PrayerBook prayerBook) { List<Widget> servicesList = []; prayerBook.services.forEach((serviceName) { servicesList.add(new ListTile( title: new Text(serviceName.title ?? 'No title'), onTap: () { Navigator.pop(context); Navigator.push(context, new MaterialPageRoute( builder: (BuildContext context) { return ServiceView( currentService: serviceName, ); }, )); }, )); }); return servicesList; } } PrayerBooksContainer buildData() { return new PrayerBooksContainer([ new PrayerBook('language', 'book1', 'book1', [ new Service('service', 'b1 sA'), new Service('service', 'b1 sB'), new Service('service', 'b1 sC'), new Service('service', 'b1 sD'), ]), new PrayerBook('language', 'book2', 'book2', [ new Service('service', 'b2 sA'), new Service('service', 'b2 sB'), new Service('service', 'b2 sC'), ]), ]); } class PrayerBooksContainer extends Object { final List<PrayerBook> prayerBooks; PrayerBooksContainer(this.prayerBooks); } class PrayerBook extends Object { final String language; final String apiName; final String title; final List<Service> services; PrayerBook(this.language, this.apiName, this.title, this.services); } class Service extends Object { final String apiName; final String title; Service(this.apiName, this.title); }
修改后,每个onTap闭包都会正确捕获当前点击的Service实例,点击不同子项就能跳转到对应的页面了!
内容的提问来源于stack exchange,提问作者Chris Hill
相关产品推荐
相关产品推荐

