Flutter 中如何避免页面转换期间出现卡顿(动画滞后)

问题描述 投票:0回答:5

我有两页,

Page A
Page B
。 要从
Page A
过渡到
Page B
,我使用
Navigation.push()
:

  Navigator.push(
    context,
    CupertinoPageRoute(...)
  );

但是,这种过渡有很多卡顿和丢帧现象。 (是的,我在配置文件模式下运行)

我想到的一个原因是

Page B
有如此多的重型 UI 渲染(例如 Google 地图和图表),我还注意到随着页面滑动动画的发生,
Page B
渲染已经开始了。

我正在尝试了解如何改善这种体验,也许可以以某种方式预加载

Page B

我已经从 Github 问题中阅读了这个建议(tldr 使用

Future.microtask(()
),但它对我不起作用。将不胜感激任何帮助或建议。

flutter dart transition
5个回答
5
投票

如果第一次运行应用程序时屏幕过渡动画特别卡顿,但如果来回运行几次过渡就会变得更加流畅,这是 Flutter 团队以及 Android 和 iOS 内的同行都知道的问题正在努力。他们在这里提出了一个解决方法的建议:https://docs.flutter.dev/perf/shader,基于“预热着色器”构建,但由于它只预热您正在调试的特定设备上的着色器,老实说,我觉得这就像编程相当于“把它扫到地毯下”......你不会再在你的设备上看到这个问题,但它仍然存在于其他设备上!

不过,我自己找到了解决这个问题的方法,效果出奇的好!实际上,我推送了这个卡顿页面,稍等一下,然后再次弹出它,而用户却毫不知情!像这样:

import 'login_screen.dart';
import 'register_screen.dart';
import 'home_screen.dart';
import 'loading_screen.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';


class WelcomeScreen extends StatefulWidget {
  const WelcomeScreen(Key? key) : super(key: key);

  @override
  _WelcomeScreenState createState() => _WelcomeScreenState();
}

class _WelcomeScreenState extends State<WelcomeScreen> {

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

  Future warmUp() async {
    // This silly function is needed to remove jank from the first run screen transition...
    print('Running warmUp()');
    await Firebase.initializeApp();
    // If not using Firebase, you'll have to add some other delay here!
    // Otherwise, you will get errors below for trying to push new screens
    // while the first one is still building.

    if (mounted) {
      Navigator.push(context, MaterialPageRoute(builder: (context) => LoginScreen(popWhenDone: false)));
      Navigator.push(context, MaterialPageRoute(builder: (context) => RegisterScreen(popWhenDone: false, userType: UserType.artist)));
      Navigator.push(context, MaterialPageRoute(builder: (context) => HomeScreen()));
      Navigator.push(context, MaterialPageRoute(builder: (context) => LoadingScreen()));  // Shows a spinner

      await Future.delayed(Duration(milliseconds: 1000));

      if (mounted) {
        Navigator.popUntil(context, (route) => route.isFirst);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    print('Building $runtimeType');
    return Scaffold(
      body: SafeArea(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                MaterialButton(
                  child: const Text('Sign Up'),
                  onPressed: () {
                    Navigator.push(context, MaterialPageRoute(builder: (context) {
                      return const RegisterScreen();
                    }));
                  },
                ),
                MaterialButton(
                  child: const Text('Log In'),
                  onPressed: () async {
                    await Firebase.initializeApp(); // In case I remove the warmUp() later...
                    if (mounted) {
                      Navigator.push(
                        context,
                        PageRouteBuilder(
                          pageBuilder: (context, a1, a2) {
                            return LoginScreen();
                          },
                          transitionsBuilder: (context, a1, a2, child) {
                            return child; // I want only a Hero animation between the screens, nothing else
                          },
                          transitionDuration: const Duration(milliseconds: 1000),
                        ),
                      );
                    }
                  },
                ),
              ],
            ),
            Expanded(
              flex: 4,
              child: Hero(tag: 'logoWText', child: Image(image: AssetImage(kImageLogoWText))),
            ),
          ],
        ),
      ),
    );
  }
}

这会广告等待应用程序加载一秒钟,并显示一个旋转器,但我发现大多数时候,旋转器几乎没有时间显示,无论如何,等待一秒钟是一个更好的用户体验让应用程序加载而不是在使用过程中遇到卡顿!

其他提示

如果您的屏幕转换仍然卡顿,即使您来回运行它们,那么您可能需要调整所涉及屏幕的性能。也许是您的构建方法太大,并且其中发生了太多事情?也许某些小部件在屏幕的每次构建过程中都会被重建多次?

查看这些建议,看看是否有帮助:https://docs.flutter.dev/perf/best-practices 无论如何,它们总体上应该可以提高应用程序的性能。 🙂

编辑:并查看此链接,由 ch271828n 在下面的评论中提供:https://github.com/fzyzcjy/flutter_smooth


1
投票

如果您在没有着色器编译问题的情况下遇到卡顿,那么确实有一种(新)方法可以使其在不更改代码的情况下达到约 60FPS 的流畅度(实际上,只需要添加 6 个字符 - CupertinoPageRoute -> SmoothCupertinoPageRoute)。

GitHub主页:https://github.com/fzyzcjy/flutter_smooth

确实,我个人在我的应用程序中经常看到这样的卡顿(即不是由着色器编译引起的卡顿),可能是因为新页面相当复杂。

免责声明:我写了那个包;)


0
投票

尝试在加载页面 B 中的初始任务之前添加一个小的延迟。也许使用 Future.delayed()


0
投票

您从

Page B
build()
返回的顶级小部件是什么?

我的是一个

FutureBuilder
,它正在建造一个
Scaffold
。我刚刚交换了它们(顶部
Scaffold
),问题就消失了。


0
投票

正如

Kaushik Chandru
所提到的,我在不同的实现上也有类似的问题。我通过将所有进程分成两组来解决它。我使用了 2 个辅助状态来管理动画。一般是这样的:

// declare your animation duration
var myAnimationDuration = Duration(miliseconds: 300);

// declare 2 boolean to separate processes into 2 group
bool isBasicsLoaded = false;
bool isAdvancedLoaded = false;

// create a function that do the process
void initialProcess() async {
   basicProcess(); // do your basic process here
   animate(duration : myAnimationDuration); // or maybe setState() if you placed some animated widgets
   await Future.delayed(myAnimationDuration);
   advancedProcess(); // do your advanced process after the initial animation done
   if(mounted) setState(() {}); // in case you need to setState safely in an async function
}

// call that function from your initState
void initState() {
   super.initState();
   initialProcess();
}

当然,作为一名优秀的程序员,您应该确定在页面初始化时哪个过程是真正至关重要,以及哪个过程可以延迟(例如获取数据或昂贵的计算/构建)——并且您可以肯定会在 UI 上返回加载标记,或者闪烁的小部件。

然后在构建方法中,您可以在返回之前使用 if 检查,如下所示:

if(!isBasicsLoaded || !isAdvancedLoaded) const Text('processing');

if(isBasicsLoaded) const Text('This Is The Title!');
if(isAdvancedLoaded) Column(children : [
    Text('my fetched data : $forExampleApiResponse');
    Text('my expensive calculations : $forExampleRenderingGoogleMap');
]);

我注意到你正在使用

Google Map
,这肯定需要一些时间来启动。您可以制作一个 zonk 框用于初始视图,因此至少您案例中的
page B
会首先加载。然后稍后您可以运行该
Google Map
渲染(就像我所做的那样,在所有基本元素加载后
setState
为 true)。另一种选择,如果您喜欢无状态的,您也可以使用内置的 FutureBuilder。

© www.soinside.com 2019 - 2024. All rights reserved.