You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:44:13