Flutter实战之闪屏启动页

在APP启动之前,一般都会SplashPage页面,这页面包含闪屏启动页(启动APP的过渡页面)、引导页(APP简介说明)、广告页(点击在浏览器中打开H5页面、或者直接下载APP文件)。

实现思路

APP的第一个页面就是闪屏启动页面,然后再处理跳转H5广告页面,还是跳转到首页的逻辑。SplashPage分为四层,默认启动图,引导图,广告图,倒计时跳过 使用status来控制页面显示状态,status=0显示启动图,status=1显示广告图和倒计时跳过,status=2显示引导图。

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {

    final router = Router();
    Routers.configureRouters(router);
    Application.router = router;

    return MaterialApp(
      title: 'Flutter Demo',
      onGenerateRoute: Application.router.generator,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SplashPage(),
    );
  }
}

依赖的第三方库

  1. fluro Flutter路由管理
  2. shared_preferences 简单数据的持久化存储
  3. cached_network_image 显示网络图片并保存到cache中
  4. url_launcher 打开URL。包括打电话、发短信、在浏览器中打开地址等URL
  5. flutter_inappbrowser 内联webview或应用程序内浏览器窗口
  6. flukit Flutter 常用库集合

以上的库都是比较常用的,当然也可以自行选择其他库或者不使用。

实现步骤

一、解决Android启动白屏

在Android原生的mipmap目录下放闪屏背景图,可以根据不同分辨率放入。
然后修改Android原生的drawable目录下的launch_background.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@android:color/white" />

    <!-- You can insert your own image assets here -->
    <!-- <item>
        <bitmap
            android:gravity="center"
            android:src="@mipmap/launch_image" />
    </item> -->
    <!-- 撑满屏幕、可能会有拉伸 -->
    <item android:drawable="@mipmap/splash_bg"></item>
</layer-list>

二、构建基础页面

根据status,使用Stack + Offset来显示不同的页面。使用Stack + Align来绝对定位部分子组件。注意:必须先把图片放到assets/images文件夹下,然后在pubspec.yaml中定义

// 构建闪屏背景
Widget _buildSplashBg() {
  return new Image.asset(
    'assets/images/splash_bg.png',
    width: double.infinity,
    height: double.infinity,
    fit: BoxFit.fill,
  );
}

// 构建广告
Widget _buildAdWidget() {
  return new InkWell(
      onTap: () {
        print('点击了广告图');
      },
      child: new Container(
        alignment: Alignment.center,
        child: new CachedNetworkImage(
          width: double.infinity,              
          height: double.infinity,
          fit: BoxFit.fill,
          imageUrl: _splashModel.imgUrl,
          placeholder: (context, url) => _buildSplashBg(),
          errorWidget: (context, url, error) => _buildSplashBg(),
        ),
      ),
    ),
  );
}

// 构建引导页
_initGuideBanner() {
  setState(() {
    _status = 2;
  });
  for (int i = 0, length = _guideList.length; i < length; i++) {
    if (i == length - 1) {
      _bannerList.add(new Stack(
        children: <Widget>[
          new Image.asset(
            _guideList[i],
            fit: BoxFit.fill,
            width: double.infinity,
            height: double.infinity,
          ),
          new Align(
            alignment: Alignment.bottomCenter,
            child: new Container(
              margin: EdgeInsets.only(bottom: 160.0),
              child: new InkWell(
                onTap: () {
                  _goMain();
                },
                child: new CircleAvatar(
                  radius: 48.0,
                  backgroundColor: Colors.indigoAccent,
                  child: new Padding(
                    padding: EdgeInsets.all(2.0),
                    child: new Text(
                      '立即体验',
                      textAlign: TextAlign.center,
                      style: TextStyle(color: Colors.white, fontSize: 16.0),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ],
      ));
    } else {
      // print(_guideList[i]);
      _bannerList.add(new Image.asset(
        _guideList[i],
        fit: BoxFit.fill,
        width: double.infinity,
        height: double.infinity,
      ));
    }
  }
}

// Stack + Offset。Swiper来自于flukit
@override
Widget build(BuildContext context) {
  return new Material(
    child: new Stack(
      children: <Widget>[
        new Offstage(
          offstage: !(_status == 0),
          child: _buildSplashBg(),
        ),
        new Offstage(
          offstage: !(_status == 2),
          child: _bannerList.isEmpty
              ? new Container()
              : new Swiper(
                  autoStart: false,
                  circular: false,
                  indicator: CircleSwiperIndicator(
                    radius: 4.0,
                    padding: EdgeInsets.only(bottom: 30.0),
                    itemColor: Colors.black26,
                  ),
                  children: _bannerList),
        ),
        _buildAdWidget(),
        new Offstage(
          offstage: !(_status == 1),
          child: new Container(
            alignment: Alignment.bottomRight,
            margin: EdgeInsets.all(20.0),
            child: InkWell(
              onTap: () {
                _goMain();
              },
              child: new Container(
                  padding: EdgeInsets.all(12.0),
                  child: new Text(
                    '$_count 跳转',
                    style: new TextStyle(fontSize: 14.0, color: Colors.white),
                  ),
                  decoration: new BoxDecoration(
                      color: Color(0x66000000),
                      borderRadius: BorderRadius.all(Radius.circular(4.0)),
                      border: new Border.all(
                          width: 0.33, color: Colors.grey))),
            ),
          ),
        )
      ],
    ),
  );

三、实现闪屏相关逻辑

倒计时。使用Timer.periodic

_timer = Timer.periodic(new Duration(seconds: 1), (timer) {
  setState(() {
    if (_count <= 1) {
      _timer.cancel();
      _timer = null;
      _goMain();
    } else {
      _count = _count - 1;
    }
  });
});

判断是否已经加载过引导图

SharedPreferences prefs = await SharedPreferences.getInstance();
bool isGuide = prefs.getBool(Constant.key_guide) ?? true;
// 是否已经加载过引导图
if (isGuide) {
  _initGuideBanner();
} else {
  _initSplash();
}

完整代码

splash_page.dart

import 'package:fluro/fluro.dart';
import 'package:flutter/material.dart';
import 'package:music_app/router/routers.dart';
import 'package:quiver/strings.dart';
import 'dart:convert';
import 'dart:async';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flukit/flukit.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:music_app/utils/http_utils.dart';
import 'package:music_app/common/common.dart';
import 'package:music_app/models/splash_model.dart';
import 'package:music_app/router/application.dart';

class SplashPage extends StatefulWidget {
  @override
  _SplashPageState createState() => _SplashPageState();
}

class _SplashPageState extends State<SplashPage> {

  List<String> _guideList = [
    'assets/images/guide1.png',
    'assets/images/guide2.png',
    'assets/images/guide3.png',
    'assets/images/guide4.png'
  ];

  List<Widget> _bannerList = new List();

  Timer _timer;
  int _status = 0; // 0-启动图,1-广告图和倒计时跳过,2-引导图。
  int _count = 3; // 倒计时秒数

  SplashModel _splashModel;

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

  void _initAsync() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    // 获取上次存储的闪屏页面
    String jsonStr = prefs.getString(Constant.key_splash_model);
    if (isNotEmpty(jsonStr)) {
      Map<String, dynamic> splash = json.decode(jsonStr);
      _splashModel = SplashModel.fromJson(splash);
    }

    HttpUtils httpUtils = new HttpUtils();
    // 获取闪屏的广告数据
    httpUtils.getSplash().then((model) {
      if (isNotEmpty(model.imgUrl)) {
        if (_splashModel == null || (_splashModel.imgUrl != model.imgUrl)) {
          prefs.setString(Constant.key_splash_model, model.toString());
          setState(() {
            _splashModel = model;
          });
        }
      } else {
        prefs.setString(Constant.key_splash_model, null);
      }
    });

    bool isGuide = prefs.getBool(Constant.key_guide) ?? true;
    // 是否已经加载过引导图
    if (isGuide) {
      _initGuideBanner();
    } else {
      _initSplash();
    }
  }

  _initGuideBanner() {
    setState(() {
      _status = 2;
    });
    for (int i = 0, length = _guideList.length; i < length; i++) {
      if (i == length - 1) {
        _bannerList.add(new Stack(
          children: <Widget>[
            new Image.asset(
              _guideList[i],
              fit: BoxFit.fill,
              width: double.infinity,
              height: double.infinity,
            ),
            new Align(
              alignment: Alignment.bottomCenter,
              child: new Container(
                margin: EdgeInsets.only(bottom: 160.0),
                child: new InkWell(
                  onTap: () {
                    _goMain();
                  },
                  child: new CircleAvatar(
                    radius: 48.0,
                    backgroundColor: Colors.indigoAccent,
                    child: new Padding(
                      padding: EdgeInsets.all(2.0),
                      child: new Text(
                        '立即体验',
                        textAlign: TextAlign.center,
                        style: TextStyle(color: Colors.white, fontSize: 16.0),
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ],
        ));
      } else {
        // print(_guideList[i]);
        _bannerList.add(new Image.asset(
          _guideList[i],
          fit: BoxFit.fill,
          width: double.infinity,
          height: double.infinity,
        ));
      }
    }
  }

  _initSplash() {
    if (_splashModel == null) {
      _goMain();
      return;
    }
    setState(() {
      _status = 1;
    });

    // 倒计时
    _timer = Timer.periodic(new Duration(seconds: 1), (timer) {
      setState(() {
        if (_count <= 1) {
          _timer.cancel();
          _timer = null;
          _goMain();
        } else {
          _count = _count - 1;
        }
      });
    });
  }

  // 跳转主页
  void _goMain() {
    Application.router.navigateTo(context, Routers.mainPage, replace: true, transition: TransitionType.fadeIn);
  }

  // 构建闪屏背景
  Widget _buildSplashBg() {
    return new Image.asset(
      'assets/images/splash_bg.png',
      width: double.infinity,
      fit: BoxFit.fill,
      height: double.infinity,
    );
  }

  // 构建广告
  Widget _buildAdWidget() {
    if (_splashModel == null) {
      return new Container(
        height: 0.0,
      );
    }
    return new Offstage(
      offstage: !(_status == 1),
      child: new InkWell(
        onTap: () async {
          String url = _splashModel.url;
          if (isEmpty(url)) return;
          _goMain();
          if (url.endsWith(".apk")) {
            // 打开浏览器下载APK
            if (await canLaunch(url)) {
              await launch(url, forceSafariVC: false, forceWebView: false);
            }
          } else {
            // 在浏览器中打开url
            Application.router.navigateTo(context, Routers.adPage + '?title=${Uri.encodeComponent(_splashModel.title)}&url=${Uri.encodeComponent(_splashModel.url)}', transition: TransitionType.fadeIn);
          }
        },
        child: new Container(
          alignment: Alignment.center,
          child: new CachedNetworkImage(
            width: double.infinity,              
            height: double.infinity,
            fit: BoxFit.fill,
            imageUrl: _splashModel.imgUrl,
            placeholder: (context, url) => _buildSplashBg(),
            errorWidget: (context, url, error) => _buildSplashBg(),
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    _timer?.cancel();
    _timer = null;
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return new Material(
      child: new Stack(
        children: <Widget>[
          new Offstage(
            offstage: !(_status == 0),
            child: _buildSplashBg(),
          ),
          new Offstage(
            offstage: !(_status == 2),
            child: _bannerList.isEmpty
                ? new Container()
                : new Swiper(
                    autoStart: false,
                    circular: false,
                    indicator: CircleSwiperIndicator(
                      radius: 4.0,
                      padding: EdgeInsets.only(bottom: 30.0),
                      itemColor: Colors.black26,
                    ),
                    children: _bannerList),
          ),
          _buildAdWidget(),
          new Offstage(
            offstage: !(_status == 1),
            child: new Container(
              alignment: Alignment.bottomRight,
              margin: EdgeInsets.all(20.0),
              child: InkWell(
                onTap: () {
                  _goMain();
                },
                child: new Container(
                    padding: EdgeInsets.all(12.0),
                    child: new Text(
                      '$_count 跳转',
                      style: new TextStyle(fontSize: 14.0, color: Colors.white),
                    ),
                    decoration: new BoxDecoration(
                        color: Color(0x66000000),
                        borderRadius: BorderRadius.all(Radius.circular(4.0)),
                        border: new Border.all(
                            width: 0.33, color: Colors.grey))),
              ),
            ),
          )
        ],
      ),
    );
  }
}

splash_model.dart

class SplashModel {
  String title;
  String content;
  String url;
  String imgUrl;

  SplashModel({this.title, this.content, this.url, this.imgUrl});

  SplashModel.fromJson(Map<String, dynamic> json)
      : title = json['title'],
        content = json['content'],
        url = json['url'],
        imgUrl = json['imgUrl'];

  Map<String, dynamic> toJson() => {
        'title': title,
        'content': content,
        'url': url,
        'imgUrl': imgUrl,
      };

  @override
  String toString() {
    StringBuffer sb = new StringBuffer('{');
    sb.write("\"title\":\"$title\"");
    sb.write(",\"content\":\"$content\"");
    sb.write(",\"url\":\"$url\"");
    sb.write(",\"imgUrl\":\"$imgUrl\"");
    sb.write('}');
    return sb.toString();
  }
}

本文参考wanandroid,感谢鸿洋大神

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Flutter闪屏可以通过Flutter启动流程来实现。在Flutter中,应用程序的入口是main()函数,在main()函数中,我们可以使用Flutter提供的WidgetsFlutterBinding.ensureInitialized()方法来初始化Flutter的绑定。 在初始化完成后,我们可以使用Navigator.pushReplacement()方法来实现闪屏的效果。具体步骤如下: 1.在main()函数中,初始化Flutter的绑定: void main() async { WidgetsFlutterBinding.ensureInitialized(); ... } 2.创建闪屏的Widget,并在Widget的build()方法中调用Navigator.pushReplacement()方法: class SplashScreen extends StatelessWidget { @override Widget build(BuildContext context) { Future.delayed(Duration(milliseconds: 3000), () { Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => HomePage()), ); }); return Scaffold( backgroundColor: Colors.white, body: Center( child: Text( 'Splash Screen', style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold), ), ), ); } } 在上面的代码中,我们使用Future.delayed()方法来延迟3秒钟,然后调用Navigator.pushReplacement()方法来跳转到主。在实际开发中,我们可以根据需要设置延迟时间。 3.在main()函数中,调用runApp()方法并传入闪屏的Widget: void main() async { WidgetsFlutterBinding.ensureInitialized(); runApp(MaterialApp(home: SplashScreen())); } 这样,当应用程序启动时,就会显示闪屏,延迟一定时间后自动跳转到主
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值