Flutter如何将启动画面保持3秒钟?如何在Flutter中实现启动画面?

19

如何在Flutter中显示闪屏界面3秒,然后进入登录界面。

我尝试过使用CountdownTimer,但导入无法解决。

import 'package: countDown/countDown.dart';
CountDown cd  =  new CountDown(new Duration(seconds: 4));
CountDown is unresolved 

Android Studio 和 Flutter

11个回答

35

我在每个应用程序中都使用的简单解决方案。

在构建方法中使用Timer类 代码片段

class SplashScreen extends StatefulWidget {
  @override
  Splash createState() => Splash();
}

class Splash extends State<SplashScreen>  {

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

  }
  @override
  Widget build(BuildContext context) {
        Timer(
            Duration(seconds: 3),
                () =>
            Navigator.of(context).pushReplacement(MaterialPageRoute(
                builder: (BuildContext context) => LandingScreen())));


    var assetsImage = new AssetImage(
        'images/new_logo.png'); //<- Creates an object that fetches an image.
    var image = new Image(
        image: assetsImage,
        height:300); //<- Creates a widget that displays an image.

    return MaterialApp(
      home: Scaffold(
        /* appBar: AppBar(
          title: Text("MyApp"),
          backgroundColor:
              Colors.blue, //<- background color to combine with the picture :-)
        ),*/
        body: Container(
          decoration: new BoxDecoration(color: Colors.white),
          child: new Center(
            child: image,
          ),
        ), //<- place where the image appears
      ),
    );
  }
}

如果我按返回按钮,那么我不会回到启动屏幕吗? - GunJack
当您按下返回按钮时,当前情况是什么?您能告诉我,这样我就可以更新答案。 - Quick learner
我理解你所做的,并且现在我已经让它工作了。 - GunJack
我知道这不是主题,但在显示启动画面时,我能在后台获取JSON数据吗? - GunJack
1
你需要使用一个后台插件在后台执行任务,但是该插件可能无法在IOS平台上工作。如果我必须在后台获取数据,我会寻找其他方法。 - Quick learner
显示剩余2条评论

20

请参考下方的 main.dart 文件。

import 'dart:async';    
import 'package:flutter/material.dart';    
import 'src/login_screen.dart';

void main() {
  runApp(new MaterialApp(
    home: new MyApp(),
  ));
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  void initState() {
    super.initState();
    new Future.delayed(
        const Duration(seconds: 3),
        () => Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => LoginScreen()),
            ));
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      backgroundColor: Colors.white,
      body: Container(
        child: new Column(children: <Widget>[
          Divider(
            height: 240.0,
            color: Colors.white,
          ),
          new Image.asset(
            'assets/logo.png',
            fit: BoxFit.cover,
            repeat: ImageRepeat.noRepeat,
            width: 170.0,
          ),
          Divider(
            height: 105.2,
            color: Colors.white,
          ),
        ]),
      ),
    );
  }
}

希望这能帮到你


13

您可以使用Future.delayed来延迟执行代码。

new Future.delayed(const Duration(seconds: 3), () {
  Navigator.pushNamed(context, '/login');
});

更新

const delay = 3;
widget.countdown = delay;

StreamSubscription sub;
sub = new Stream.periodic(const Duration(seconds: 1), (count) {
  setState(() => widget.countdown--);  
  if(widget.countdown <= 0) {
    sub.cancel();
    Navigator.pushNamed(context, '/login');
  }
});     

我应该在哪里调用这个未来的代码。我的意思是,我不想在onClick上调用它。 - Deepak Ror
@RémiRousselet 你知道为什么带有指向你答案链接的评论被删除了吗? - Günter Zöchbauer
我已将其删除。最终考虑到他要求倒计时,这并不是一个真正的重复问题。 - Rémi Rousselet
我认为这只是闪屏的最佳解决方案。= new Timer(const Duration(seconds: 5), goToLogin);。其中gotoLogin(){}。 - Deepak Ror
@RémiRousselet 很好。没看到 :D - Günter Zöchbauer
显示剩余7条评论

4
Future.delayed是一种很好的解决方案,没有倒计时的情况下。
但是考虑到您有一个倒计时,您可以使用Flutter提供的动画框架。
其背后的想法是使用持续3秒钟的AnimationController。在实例化splashScreen时立即启动动画。并添加一个监听器,在动画结束时重定向到/login
然后将该控制器传递给一个AnimationBuilder,它将根据animationController.lastElaspedDuration处理您的倒计时格式。
class SplashScreen extends StatefulWidget {
  final Duration duration;

  const SplashScreen({this.duration});

  @override
  _SplashScreenState createState() => new _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen> with SingleTickerProviderStateMixin {
  AnimationController animationController;

  @override
  void initState() {
    animationController = new AnimationController(duration: widget.duration, vsync: this)
      ..forward()
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          Navigator.pushReplacementNamed(context, '/login');
        }
      });
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return new AnimatedBuilder(
      animation: animationController,
      builder: (context, _) {
        return new Center(
          child: new Text(animationController.lastElapsedDuration.inSeconds.toString()),
        );
      },
    );
  }
}

3

我需要一个带有5秒延迟的小部件。我的解决方案如下:

class Waiting extends StatefulWidget {
  @override
  _WaitingState createState() => _WaitingState();
}

class _WaitingState extends State<Waiting> {
  bool voxt = false;

  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: Future.delayed(Duration(seconds: 3)),
      builder: (c, s) => s.connectionState != ConnectionState.done
          ? Text('Waiting')
          : Text('3 sec passed')
    );
  }
}

现在,等待小部件可以在需要的地方调用。

2
你也可以在 StatlessWidget() 中创建一个 splashScreen。在 MaterialApp() 中的 home: 下面:
home: FutureBuilder(
        future: Future.delayed(Duration(seconds: 3)),
        builder: (ctx, timer) => timer.connectionState == ConnectionState.done
            ? ProfileScreen() //Screen to navigate to once the splashScreen is done.
            : Container(
                color: Colors.white,
                child: Image(
                  image: AssetImage('assets/images/download.png'),
                ),
              )),

1

最干净的方法,无需添加显式计时器。

使用基于时间的 SplashScreen

class TimeBasedSplash extends State<MyApp>{

  @override
  Widget build(BuildContext context) {
    return new SplashScreen(
      seconds: 10,
      navigateAfterSeconds: new HomeScreen(),// Where to navigate after 10 secs
      image: new Image.asset('assets/images/flutter_logo.png'),
      photoSize: 200,
      loaderColor: Colors.white,
      styleTextUnderTheLoader : const TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold, color: Colors.white),
     loadingText: new Text('Loading...'),
      gradientBackground: LinearGradient(
        begin: Alignment.topCenter,
        end: Alignment.bottomCenter,
        colors: <Color>[
          Colors.lightBlue,
          Colors.indigo
        ],
      ),
    );
  }

}

在主类中
void main(){
  runApp(new MaterialApp(
    home: new MyApp(),
  ));
}


class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return new TimeBasedSplash().build(context);
  }
}

0

我认为你需要清除堆栈中的旧活动(启动屏幕),因此你必须使用pushNamedAndRemoveUntil而不是仅使用pushNamed

  new Future.delayed(const Duration(seconds: 3), () {
  Navigator.pushNamedAndRemoveUntil(context, '/login', ModalRoute.withName('/'));
});

或者使用 pushReplacementNamed - Rémi Rousselet

0

如果您正在使用flutter-redux,则此答案仅适用于该情况。

除了flutter-redux之外,您还需要使用redux-persist库来显示加载屏幕。

redux-persist用于存储和重新启动应用程序状态。

示例:

1.main.dart

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux_persist_flutter/redux_persist_flutter.dart';

import 'package:flutter_redux_starter/presentation/platform_adaptive.dart';
import 'package:flutter_redux_starter/screens/loading_screen.dart';
import 'package:flutter_redux_starter/store/store.dart';
import 'package:flutter_redux_starter/middleware/middleware.dart';
import 'package:flutter_redux_starter/models/app_state.dart';
import 'package:flutter_redux_starter/routes.dart';


void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
    final store = createStore();

    MyApp();

    @override
    Widget build(BuildContext context) {
        return new PersistorGate(
            persistor: persistor,
            loading: new LoadingScreen(),
            builder: (context) => new StoreProvider<AppState>(
                store: store,
                child: new MaterialApp(
                    title: 'Flutter test App',
                    theme: defaultTargetPlatform == TargetPlatform.iOS
                        ? kIOSTheme
                        : kDefaultTheme,
                routes: getRoutes(context, store),
                    initialRoute: '/login',
                )
            ),
        );
    }

}

2.store.dart

import 'package:redux/redux.dart';

import 'package:flutter_redux_starter/reducers/app_reducer.dart';
import 'package:flutter_redux_starter/models/app_state.dart';
import 'package:flutter_redux_starter/middleware/middleware.dart';

Store<AppState> createStore() { 
    Store<AppState> store = new Store(
        appReducer,
        initialState: new AppState(),
        middleware: createMiddleware(),
    );
    persistor.start(store);

    return store;
}

在createStore中,您可以使用Future.delayed来延迟创建存储器的时间,以达到一定的秒数。
new Future.delayed(const Duration(seconds: 3), () {
 // 
});

0

这是我的启动画面方法,这种方法的优点是确保应用程序启动时只启动一次启动画面。

首先在应用程序主页类中定义一个静态布尔值来指示应用程序是否已启动。

static bool launch = true;

然后在您的MaterialApp小部件中的主页属性中,在应用程序主页类中检查(launch)是否为true,如果是,则使用FutureBuilder启动闪屏屏幕,如果(launch)为false,则将主页设置为第二个屏幕。使用FutureBuilder,您可以为您的闪屏屏幕设置一个计时器,当它完成时,您的第二个屏幕将开始(感谢O'neya的答案https://dev59.com/fVUL5IYBdhLWcg3wlI1w#68699447)。

home: launch? FutureBuilder(
        future: Future.delayed(const Duration(seconds: 3)),
        builder: (ctx, timer) =>
        timer.connectionState == ConnectionState.done
            ? const SecondScreen(title: 'Flutter Demo Home Page')
            : appSplashScreen(),
      ): const SecondScreen(title: 'Flutter Demo Home Page'),

在第二个屏幕中,检查 (launch) 是否为 true,然后将其设置为 false。这将确保启动屏幕每次应用程序启动时只会启动一次。
if(AppHome.launch) {
      AppHome.launch = false;
    }

以下是完整的代码,其中包含底部的appSplashScreen小部件:

import 'package:flutter/material.dart';

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

class AppHome extends StatelessWidget {
  const AppHome({Key? key}) : super(key: key);

  //static bool to indicate the launching of the app
  static bool launch = true;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: launch? FutureBuilder(
        future: Future.delayed(const Duration(seconds: 3)),
        builder: (ctx, timer) =>
        timer.connectionState == ConnectionState.done
            ? const SecondScreen(title: 'Flutter Demo Home Page')
            : appSplashScreen(),
      ): const SecondScreen(title: 'Flutter Demo Home Page'),
    );
  }
}

class SecondScreen extends StatefulWidget {
  const SecondScreen({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  State<SecondScreen> createState() => _SecondScreenState();
}

class _SecondScreenState extends State<SecondScreen> {

  @override
  Widget build(BuildContext context) {
    //mack sure your splash screen only launch once at your app starting
    if(AppHome.launch) {
      AppHome.launch = false;
    }
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: const Center(
        child: Text(
          'My Second screen',
        ),
      ),
    );
  }
}

Widget appSplashScreen() {
  return Container(
    decoration: const BoxDecoration(
      ////you can add background image/color to your splash screen
      // image: DecorationImage(
      //   image: AssetImage('assets/background.png'),
      //   fit: BoxFit.cover,
      // ),
      color: Colors.white,
    ),
    child: Center(
      child: SizedBox(
        //get MediaQuery from instance of window to get height and width (no need of context)
        height: MediaQueryData.fromWindow(WidgetsBinding.instance.window).size.height*0.5,
        width: MediaQueryData.fromWindow(WidgetsBinding.instance.window).size.width*0.5,
        child: Column(
          children: const [
            ////you can add image to your splash screen
            // Image(
            //   image: AssetImage('assets/splashscreen_image.png'),
            // ),
            FittedBox(
                child: Text(
                  'Loading',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    decoration: TextDecoration.none,
                  ),
                )
            ),
            CircularProgressIndicator(),
          ],
        ),
      ),
    ),
  );
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接