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

Flutter实现M3 List-detail规范布局:寻求Navigator替代布局操控方案

优化M3列表详情(List-Detail)布局的Navigator实现方式

问题背景

我在实现M3规范中的List-detail经典布局时遇到了问题,这种布局类似Gmail左侧显示列表、右侧展示选中项详情,或是WhatsApp左侧聊天列表、右侧显示对话内容的样式。

我当前的实现是通过直接操控布局来切换列表和详情页,但想改用Navigator和独立页面的方式实现,希望找到更优的方案。

当前实现代码

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'List-detail',
      home: ListDetailScreen(),
    );
  }
}

class ListDetailScreen extends StatefulWidget {
  const ListDetailScreen({super.key});

  @override
  State<ListDetailScreen> createState() => _ListDetailScreenState();
}

class _ListDetailScreenState extends State<ListDetailScreen> {
  bool largeScreen = false;
  int? selectedIndex;

  @override
  void initState() {
    super.initState();
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();

    final double width = MediaQuery.of(context).size.width;
    largeScreen = width > 600;
  }

  @override
  Widget build(BuildContext context) {
    if (largeScreen) {
      return Row(
        children: [
          Flexible(
            flex: 1,
            child: ListScreen(
              onSelectedItem: onSelectedItem,
            ),
          ),
          Flexible(
            flex: 1,
            child: DetailScreen(
              index: selectedIndex,
            ),
          ),
        ],
      );
    } else {
      if (selectedIndex == null) {
        return ListScreen(
          onSelectedItem: onSelectedItem,
        );
      } else {
        return DetailScreen(
          index: selectedIndex,
        );
      }
    }
  }

  void onSelectedItem(int index) {
    setState(() {
      selectedIndex = index;
    });
  }
}

class ListScreen extends StatefulWidget {
  final void Function(int) onSelectedItem;

  const ListScreen({super.key, required this.onSelectedItem});

  @override
  State<ListScreen> createState() => _ListScreenState();
}

class _ListScreenState extends State<ListScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('List'),
        elevation: 0,
      ),
      body: ListView(
        children: List.generate(100, (index) {
          return ListTile(
            title: Text('Item $index'),
            onTap: () => widget.onSelectedItem(index),
          );
        }),
      ),
    );
  }
}

class DetailScreen extends StatefulWidget {
  final int? index;

  const DetailScreen({super.key, this.index});

  @override
  State<DetailScreen> createState() => _DetailScreenState();
}

class _DetailScreenState extends State<DetailScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Detail'),
        elevation: 0,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: _buildContent(),
      ),
    );
  }

  Widget _buildContent() {
    if (widget.index == null) {
      return const Text('Please select an item...');
    }
    return Text('Selected item ${widget.index}');
  }
}

基于Navigator的优化实现

我们可以通过路由管理结合自适应布局来实现,让大屏下保持双栏布局,小屏下用Navigator跳转详情页,同时保持路由栈的一致性。核心思路是:

  • 大屏时:在右侧详情栏嵌套独立Navigator,既保留双栏布局,又能通过路由管理详情页状态
  • 小屏时:直接通过全局Navigator跳转详情页

优化后的完整代码

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'List-detail with Navigator',
      home: const AdaptiveListDetailContainer(),
      routes: {
        '/detail': (context) => DetailScreen(
              index: ModalRoute.of(context)?.settings.arguments as int?,
            ),
      },
    );
  }
}

class ListScreen extends StatelessWidget {
  const ListScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final isLargeScreen = MediaQuery.of(context).size.width > 600;

    return Scaffold(
      appBar: AppBar(title: const Text('List')),
      body: ListView.builder(
        itemCount: 100,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('Item $index'),
            onTap: () {
              if (isLargeScreen) {
                // 大屏下:在右侧详情栏的Navigator中更新路由
                Navigator.of(context, rootNavigator: false).pushReplacementNamed(
                  '/detail',
                  arguments: index,
                );
              } else {
                // 小屏下:全局Navigator跳转详情页
                Navigator.pushNamed(
                  context,
                  '/detail',
                  arguments: index,
                );
              }
            },
          );
        },
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  final int? index;

  const DetailScreen({super.key, this.index});

  @override
  Widget build(BuildContext context) {
    final isLargeScreen = MediaQuery.of(context).size.width > 600;

    return Scaffold(
      appBar: AppBar(
        title: const Text('Detail'),
        // 大屏下隐藏返回按钮,符合双栏交互逻辑
        automaticallyImplyLeading: !isLargeScreen,
      ),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: index == null
            ? const Text('请选择一个项目...')
            : Text('选中的项目:$index'),
      ),
    );
  }
}

// 自适应容器:根据屏幕尺寸切换布局
class AdaptiveListDetailContainer extends StatelessWidget {
  const AdaptiveListDetailContainer({super.key});

  @override
  Widget build(BuildContext context) {
    final isLargeScreen = MediaQuery.of(context).size.width > 600;

    if (isLargeScreen) {
      return Row(
        children: [
          // 左侧列表:占1/3宽度
          const Expanded(flex: 1, child: ListScreen()),
          // 右侧详情栏:占2/3宽度,嵌套独立Navigator
          Expanded(
            flex: 2,
            child: Navigator(
              initialRoute: '/detail',
              onGenerateRoute: (settings) {
                return MaterialPageRoute(
                  builder: (context) => DetailScreen(
                    index: settings.arguments as int?,
                  ),
                );
              },
            ),
          ),
        ],
      );
    } else {
      // 小屏下直接显示列表页
      return const ListScreen();
    }
  }
}

关键优化点

  1. 路由统一管理:通过MaterialApp的routes配置统一路由,避免硬编码跳转路径
  2. 自适应Navigator嵌套:大屏下右侧详情栏使用独立Navigator,实现双栏布局下的路由状态管理,不影响左侧列表
  3. 交互细节优化:大屏下详情页自动隐藏返回按钮,贴合M3双栏布局的交互规范
  4. 规范状态传递:通过settings.arguments传递选中项索引,替代回调传参,符合Flutter路由设计原则

额外优化建议

  • 使用LayoutBuilder替代MediaQuery监听布局变化,支持窗口 resize 时自动切换布局(比如平板横竖屏切换)
  • 大屏下给详情栏添加分割线,增强视觉区分度
  • 结合状态管理工具(如Provider、Riverpod)统一管理选中状态,避免页面间频繁传参

内容的提问来源于stack exchange,提问作者Alessandro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 23:14:51