flutter ModalRoute Rebuilds Transition Widgets Every Frame

eblbsuwk  于 4个月前  发布在  Flutter
关注(0)|答案(3)|浏览(138)

重现步骤

  1. 运行代码示例
  2. 点击 pageBuilder 按钮
  3. 点击 transitionsBuilder 按钮

预期结果

pageBuildertransitionsBuilder 应该各执行一次。

实际结果

pageBuilder 执行了1次,而 transitionsBuilder 执行了38次。

代码示例

代码示例

import 'package:flutter/material.dart';

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

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

@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomeScreen(),
);
}
}

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

@override
State createState() => _HomeScreenState();
}

class _HomeScreenState extends State {
var pageBuilderCount = 0;
var transitionsBuilderCount = 0;

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home Screen'),
),
body: Center(
child: Column(
children: [
const Spacer(flex: 3),
Text('$pageBuilderCount builds'),
ElevatedButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.blue),
foregroundColor: const WidgetStatePropertyAll(Colors.white),
),
onPressed: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
++pageBuilderCount;
WidgetsBinding.instance.addPostFrameCallback(() => setState(() {}));
return FadeTransition(
opacity: animation,
child: Scaffold(
backgroundColor: Colors.blue,
appBar: AppBar(
title: const Text('pageBuilder'),
),
body: const SizedBox(),
),
);
},
),
);
},
child: const Text('pageBuilder'),
),
const Spacer(),
Text('$transitionsBuilderCount builds'),
ElevatedButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.red),
foregroundColor: const WidgetStatePropertyAll(Colors.white),
),
onPressed: () {
Navigator.push(
context,
PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
return Scaffold(
backgroundColor: Colors.red,
appBar: AppBar(
title: const Text('transitionsBuilder'),
),
body: const SizedBox(),
);
},
transitionsBuilder: (context, animation, secondaryAnimation, child) {
++transitionsBuilderCount;
WidgetsBinding.instance.addPostFrameCallback((
) => setState(() {}));
return FadeTransition(
opacity: animation,
child: child,
);
},
),
);
},
child: const Text('transitionsBuilder'),
),
const Spacer(flex: 3),
],
),
),
);
}
}

截图或视频

截图/视频演示screen_recording.mp4

日志

  • 无响应*

Flutter Doctor输出

Doctor输出

[✓] Flutter (Channel stable, 3.22.0, on macOS 14.5 23F79 darwin-arm64, locale en-US)
• Flutter version 3.22.0 on channel stable at /opt/homebrew/Caskroom/flutter/3.22.0/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 5dcb86f68f (13 days ago), 2024-05-09 07:39:20 -0500
• Engine revision f6344b75dc
• Dart version 3.4.0
• DevTools version 2.34.3

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
• Android SDK at /Users/user/Library/Android/sdk
• Platform android-34, build-tools 34.0.0
• Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 17.0.10+0-17.0.10b1087.21-11572160)
• All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 15F31d
• CocoaPods version 1.15.2

[✓] Chrome - develop for the web
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2023.3)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/9212-flutter
• Dart plugin can be installed from:
🔨 https://plugins.jetbrains.com/plugin/6351-dart
• Java version OpenJDK Runtime Environment (build 17.0.10+0-17.0.10b1087.21-11572160)

[✓] VS Code (version 1.89.1)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 3.88.0

[✓] Connected device (5 available)
• sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 14 (API 34) (emulator)
• iPhone 15 Pro Max (mobile) • 9CF6C1A2-E96A-4D3D-B878-1392E4F72A14 • ios • com.apple.CoreSimulator.SimRuntime.iOS-17-5 (simulator)
• macOS (desktop) • macos • darwin-arm64 • macOS 14.5 23F79 darwin-arm64
• Mac Designed for iPad (desktop) • mac-designed-for-ipad • darwin • macOS 14.5 23F79 darwin-arm64
• Chrome (web) • chrome • web-javascript • Google Chrome 124.0.6367.210

[✓] Network resources
• All expected network resources are available.

• No issues found!

vhipe2zx

vhipe2zx1#

ModalRoute 构建了一个 _ModalScope ,它构建了一个 ListenableBuilder ,该 ListenableBuilder 调用了 buildTransitions
来源:

child: ListenableBuilder(
                          listenable: _listenable, // immutable
                          builder: (BuildContext context, Widget? child) {
                            return widget.route.buildTransitions(
                              context,
                              widget.route.animation!,
                              widget.route.secondaryAnimation!,

ListenableBuilder 监听 animationsecondaryAnimation 的合并。
来源:

final List<Listenable> animations = <Listenable>[
      if (widget.route.animation != null) widget.route.animation!,
      if (widget.route.secondaryAnimation != null) widget.route.secondaryAnimation!,
    ];
    _listenable = Listenable.merge(animations);

这意味着 ListenableBuilder 在过渡的每一帧上都会调用 buildTransitions 。文档中说“在过渡期间,当路由可见时,每次都会调用 buildTransitions ”,但没有提到它是对每个过渡帧都有条件地调用。
来源:

/// The [buildTransitions] method, in contrast to [buildPage], is called each
/// time the [Route]'s state changes while it is visible (e.g. if the value of
/// [canPop] changes on the active route).

它也没有提到使用 buildTransitions 而不是 buildPage 时性能的影响,尽管它建议用户将他们的 transitionwidgets 放在那里。当 Material widget 层通过 MaterialRouteTransitionMixinMaterialPageRoute 与应用程序主题绑定时,会遵循这个建议。
来源:

@override
  Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
    final PageTransitionsTheme theme = Theme.of(context).pageTransitionsTheme;
    return theme.buildTransitions<T>(this, context, animation, secondaryAnimation, child);
  }

这样做会导致 MaterialApp 中的每个页面过渡都每帧产生额外的 Theme.of(context) 成本。哈希表查找和部件构建是廉价的,但是框架为什么要承担这些成本呢?尤其是在过渡期间,它必须尽快生成帧的时候?
如果有原因的话,也许框架可以记录这种行为并建议开发者将他们的过渡部件放在 buildPage 中,只要这些部件在过渡期间不会改变。另一方面,这种行为可能是一个 bug 吗?

wmtdaxz3

wmtdaxz32#

感谢您的报告。已复制上述结果。
运行flutter doctor -v (stable和master版本)

[✓] Flutter (Channel stable, 3.22.1, on macOS 14.1 23B74 darwin-x64, locale en-VN)
    • Flutter version 3.22.1 on channel stable at /Users/huynq/Documents/GitHub/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision a14f74ff3a (11 hours ago), 2024-05-22 11:08:21 -0500
    • Engine revision 55eae6864b
    • Dart version 3.4.1
    • DevTools version 2.34.3

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    • Android SDK at /Users/huynq/Library/Android/sdk
    • Platform android-34, build-tools 34.0.0
    • ANDROID_HOME = /Users/huynq/Library/Android/sdk
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.3)
    • Xcode at /Applications/Xcode15.3.app/Contents/Developer
    • Build 15E204a
    • CocoaPods version 1.15.2

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2023.2)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • android-studio-dir = /Applications/Android Studio.app/
    • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874)

[✓] VS Code (version 1.89.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.88.0

[✓] Connected device (2 available)
    • macOS (desktop) • macos  • darwin-x64     • macOS 14.1 23B74 darwin-x64
    • Chrome (web)    • chrome • web-javascript • Google Chrome 125.0.6422.76

[✓] Network resources
    • All expected network resources are available.

• No issues found!
[!] Flutter (Channel master, 3.22.0-44.0.pre.10, on macOS 14.1 23B74 darwin-x64, locale en-VN)
    • Flutter version 3.22.0-44.0.pre.10 on channel master at /Users/huynq/Documents/GitHub/flutter_master
    ! Warning: `flutter` on your path resolves to /Users/huynq/Documents/GitHub/flutter/bin/flutter, which is not inside your current Flutter SDK checkout at /Users/huynq/Documents/GitHub/flutter_master. Consider adding /Users/huynq/Documents/GitHub/flutter_master/bin to the front of your path.
    ! Warning: `dart` on your path resolves to /Users/huynq/Documents/GitHub/flutter/bin/dart, which is not inside your current Flutter SDK checkout at /Users/huynq/Documents/GitHub/flutter_master. Consider adding /Users/huynq/Documents/GitHub/flutter_master/bin to the front of your path.
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision fe0932f2c0 (76 minutes ago), 2024-05-22 22:19:25 -0400
    • Engine revision b8b82454e3
    • Dart version 3.5.0 (build 3.5.0-178.0.dev)
    • DevTools version 2.36.0-dev.10
    • If those were intentional, you can disregard the above warnings; however it is recommended to use "git" directly to perform update checks and upgrades.

[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
    • Android SDK at /Users/huynq/Library/Android/sdk
    • Platform android-34, build-tools 34.0.0
    • ANDROID_HOME = /Users/huynq/Library/Android/sdk
    • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 15.3)
    • Xcode at /Applications/Xcode15.3.app/Contents/Developer
    • Build 15E204a
    • CocoaPods version 1.15.2

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2023.2)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • android-studio-dir = /Applications/Android Studio.app/
    • Java version OpenJDK Runtime Environment (build 17.0.9+0-17.0.9b1087.7-11185874)

[✓] VS Code (version 1.89.1)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.88.0

[✓] Connected device (2 available)
    • macOS (desktop) • macos  • darwin-x64     • macOS 14.1 23B74 darwin-x64
    • Chrome (web)    • chrome • web-javascript • Google Chrome 125.0.6422.76

[✓] Network resources
    • All expected network resources are available.

! Doctor found issues in 1 category.
wooyq4lh

wooyq4lh3#

Navigator文档中提到了每帧重建过渡小部件:
来源:

/// The page route is built in two parts, the "page" and the
/// "transitions". The page becomes a descendant of the child passed to
/// the `transitionsBuilder` function. Typically the page is only built once,
/// because it doesn't depend on its animation parameters (elided with `_`
/// and `__` in this example). The transition is built on every frame
/// for its duration.

如果这是有意为之,我很好奇原因。过渡小部件不需要重建,因为它们监听动画并自动重建自己。
支持不继承自AnimatedWidget的小部件的想法是?这样的小部件可以在ModalRoute.buildTransitions的每一帧内根据animationsecondaryAnimation的值重新配置自己。确实,_ModalScopeState监听动画并提供隐式的AnimatedBuilder

相关问题