Flutter实战 从头撸一个「孤岛」APP(No.2、闪屏Splash Page、引导页)

6,751 阅读9分钟

1. 前情回顾

嗨,正在掉头发的你们好,我是洋小洋,本集咱们接着聊一下GooGle的“亲儿子”Flutter。那自从上篇文章分享之后,有一些掘友也发表了一些很有意思的评论。那咱们首先来回顾一下

1.1 读读评论

在这里挑选3位的评论,觉得挺有趣,毕竟咱也不是设计师,这也不得不证明了一个产品的UI是多么的重要,当然也是随机在评论区,贴上他们的链接,也可以看看对方会不会有什么技术上的专栏,大家互相学习。之后的每一段路,我也都对从评论区贴上你们的奇葩评论

1.2 上集成就

  • 掘金社区Flutter模块7天内热榜第1

  • 掘金社区Flutter模块热门第3

  • 掘金社区Flutter模块最新第8

  • 截止目前25赞10评论

虽然我也不太喜欢掘金的搜索算法,听说更新了,期待越来越好,大家能够分享自己对于技术的探索

2. 写在前面

不知不觉一周又过去了,实际开发的过程中还是会有很多坑的,这个循序渐进会和大家分享,包括像Provider和后台交互的流程等。Dio的企业化配置等。那在本集咱们还是画界面

2.1 本章目标

如图所示,还是那么一个Low的界面,下面的内容可能会有点无聊,不过真的很肝,期待与你相遇

2.2 分享列表

2.3 Flutter数据

3. 着手开发

3.1 vscode 插件

  • Flutter 语法检测、代码补全、代码重构、运行调试和热重载
  • Dart
  • Flutter Widget Snippets Widget 代码片段
  • Awesome Flutter Snippets 提供常用函数的代码片段

那由于一个APP 一上来就直接登录页或者注册页。是有点生硬的,对用户来说是极为不友好的,凡事都要慢慢来,有个过程不是吗?其实在闪屏的过程中,是会做一些判断的,你如判断用户有没有登录等

3.2 拉取代码

发现我们并没有提交更新

  • 新建开发分支

接着咱们新建一个开发分支,用来记录新的一个章节,这个章节咱们要做的是闪屏和引导页,如题

  • DEBUG 调试

在上周的时候 我们是通过在项目的根目录下flutter run运行项目,然后Rr进行切换

本周咱们借助Vscode debug 调试模式

3.3 公共变量处理Global

在评论区,有掘友提到,底部的导航也太大,其实我们是已经适配过屏幕的不是吗?通过一个插件flutter_screenutil,当然也可以采取其他的方式,比如MediaQuery可以拿到widget以及设备的的宽高

那么什么是全局变量呢, 全局变量就是单纯指会贯穿整个APP生命周期的变量,用于单纯的保存一些信息,或者封装一些全局工具和方法的对象。

我们先打算新建一个global.dart用来存放比如说常用的宽度、高度、以及字体大小

cd utils
copy nul > global.dart // 新建一个global.dart(windows下)
import 'package:flutter_screenutil/flutter_screenutil.dart';

/// 页面常见的宽度与高度
// 宽度
double width100 = ScreenUtil.getInstance().setWidth(100);
// 高度
double height100 = ScreenUtil.getInstance().setHeight(100);

接着我们在底部的导航栏看下直接用我们的变量,当然第一步还是引入

import '../utils/global.dart';

我们来进行测试下,把上一段结尾咱们引入的图片全部先删除,写上一段测试文字

当我们重启之后,显然是可以的

3.4 闪屏 splash screen

我们第一步还是新建一个page页面,并命名为splash_screen_page

/// 闪屏的页面
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Text('闪屏页面'),
      ),
    );
  }
}

然后在main.dart引用并使用,这时我们已经不再引入底部的导航界面了

   return MaterialApp(
        debugShowCheckedModeBanner: false, // 去除调试
        title: '孤岛',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: SplashScreenPage()); // 这里使用引导页,也就是说当用户一进来的时候,是一个闪屏
  }

接着咱们就写属于这个孤岛APP 闪屏页,首先了解下几个部件

  • SingleChildScrollView 主要是控制里边的部件溢出,可以进行滚动

  • BoxDecoration 主要是对容器进行修饰的,比如背景色,宽高等

这里我们使用一下渐变 gradient

 decoration: BoxDecoration(
            gradient: LinearGradient(
                colors: [
              // 线性渐变 有个渐变的过程
              Color.fromRGBO(0, 0, 0, 0.2),
              Color.fromRGBO(0, 0, 0, 0.4)
            ],
                begin: FractionalOffset.topCenter, // 顶部居中
                end: FractionalOffset.bottomCenter)), // 

效果是这个样子的

这个时候应用的第一个页面便是闪屏页面,然后我们写这个页面的主要内容

  child: Container(
              child: Column(
                children: <Widget>[
                  Padding(
                    padding: EdgeInsets.only(top: 30),
                  ),
                    //  
                  Text('孤岛',
                      style: TextStyle(
                          fontSize: fontSize200, fontWeight: FontWeight.w600))
                ],
              ),
            ),

这里注意,fontSize: fontSize200我们直接使用公共的global的声明定义好的大小就可

好了 跑一下

不尽如意的是,报了错误

The method '/' was called on null.

这个问题很简单,其实是我们在使用屏幕适配的时候,并没有初始化

显然这样就可以了,我们接着插入一张图片,放在这儿就会好些了

  decoration: BoxDecoration(
            image: DecorationImage(
              image: AssetImage(assetName)
            )
          ),

放什么图片呢,就还是放一张保守点的吧,感觉她越来越不像《孤岛App》了

**图片资源来自B站UP主 **wlop- (如有侵权,请联系 Blog

老规矩咱们还是需要在pubspec.yaml中配置一下,闪屏静态时候的样子咱们暂且这个样子

还记得我们上一段旅程有一起看过MaterialApp 的源码

  const MaterialApp({
    Key key,
    this.navigatorKey,
    this.home,
    this.routes = const <String, WidgetBuilder>{}, // 我们暂时会用到这个路由的配置
    this.initialRoute,
    this.onGenerateRoute,
    this.onUnknownRoute,
    this.navigatorObservers = const <NavigatorObserver>[],
    this.builder,
    this.title = '',
    this.onGenerateTitle,
    this.color,
    this.theme,
    this.darkTheme,
    this.themeMode = ThemeMode.system,
    this.locale,
    this.localizationsDelegates,
    this.localeListResolutionCallback,
    this.localeResolutionCallback,
    this.supportedLocales = const <Locale>[Locale('en', 'US')],
    this.debugShowMaterialGrid = false,
    this.showPerformanceOverlay = false,
    this.checkerboardRasterCacheImages = false,
    this.checkerboardOffscreenLayers = false,
    this.showSemanticsDebugger = false,
    this.debugShowCheckedModeBanner = true,
  })

主要目的便是当闪屏结束的时候,跳转到引导页面

  return MaterialApp(
      debugShowCheckedModeBanner: false, // 去除调试
      title: '孤岛',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: SplashScreenPage(),
      routes: {
        'guidePages': (context) {
          return GuidePages(); // 跳转到引导页面
        }
      },
    );

设计一个计时器,用来等计时器结束的时候,跳转

 /// 设计一个计时器,用来等计时器结束的时候,跳转
  jumpPage(){
    return Timer(duration, callback);
  }
/// 源码其实是这样的
 factory Timer(Duration duration, void callback()) {
    if (Zone.current == Zone.root) {
      // No need to bind the callback. We know that the root's timer will
      // be invoked in the root zone.
      return Zone.current.createTimer(duration, callback);
    }
    return Zone.current
        .createTimer(duration, Zone.current.bindCallbackGuarded(callback));
  }

  • duration 就是延迟
  • callback 就是回调,在时间结束做什么事情

由于之前咱们是用的是StatelessWidget 也就是说我们需要在一个时间调用页面跳转的方法暂时我们改用有状态的部件StatefulWidget,完整代码是这样

import 'dart:async';

/// 闪屏的页面
import 'package:flutter/material.dart';
import '../utils/global.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';

class SplashScreenPage extends StatefulWidget {
  SplashScreenPage({Key key}) : super(key: key);

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

class _SplashScreenPageState extends State<SplashScreenPage> {
  /// 设计一个计时器,用来等计时器结束的时候,跳转
  jumpPage() {
    return Timer(Duration(milliseconds: 3000), () {
      Navigator.pushReplacementNamed(context, 'guidePages');
    });
  }

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

    jumpPage();
  }

  @override
  Widget build(BuildContext context) {
    ScreenUtil.instance = ScreenUtil(width: 750, height: 1334)..init(context);
    return Scaffold(
        backgroundColor: Colors.white, // 背景色
        body: Container(
          decoration: BoxDecoration(
              image: DecorationImage(
                  image: AssetImage('images/splash_screen.jpg'),
                  fit: BoxFit.cover)), // 添加背景图片
          child: Container(
            child: Center(
              child: SingleChildScrollView(
                child: Container(
                  child: Column(
                    children: <Widget>[
                      Padding(
                        padding: EdgeInsets.only(top: 30),
                      ),
                      Text('欢迎来到孤岛',
                          style: TextStyle(
                              fontSize: fontSize40,
                              color: Colors.white10,
                              fontWeight: FontWeight.w600)),
                      SizedBox(
                        height: height100,
                      ),
                      Text('By 洋小洋',
                          style: TextStyle(
                              fontSize: fontSize40,
                              color: Colors.white10,
                              fontWeight: FontWeight.w600))
                    ],
                  ),
                ),
              ),
            ),
            decoration: BoxDecoration(
                gradient: LinearGradient(
                    colors: [
                  // 线性渐变 有个渐变的过程
                  Color.fromRGBO(0, 0, 0, 0.2),
                  Color.fromRGBO(0, 0, 0, 0.5)
                ],
                    begin: FractionalOffset.topCenter, // 顶部居中
                    end: FractionalOffset.bottomCenter)), // 底部居中
          ),
        ));
  }
}


好了,我们先阶段性的看下一起实现的效果

3.5 引导页

在走过了闪屏之后,我们期望能有个引导页,顾名思义就是引导用户下一步下一步,这里咱们采用第三方的包intro_views_flutter 2.8.0

截止目前包的版本是2.8.0

包名 传送
intro_views_flutter intro_views_flutter
  • 将此添加到包的pubspec.yaml文件中:

    dependencies:
      intro_views_flutter: ^2.8.0
    
    
  • 您可以从命令行安装软件包:

    $ flutter pub get
    
    
  • 现在,在Dart代码中,您可以使用:

    import 'package:intro_views_flutter/intro_views_flutter.dart';
    
    

一波流操作后,就可以是使用了,具体使用的方法还是需要看一下API

属性 数据类型 描述 默认值
pageColor Color 设置页面的颜色。 Null
mainImage Image / Widget 设置页面的主图像。 Null
title Text / Widget 设置页面的标题文本。 Null
body Text / Widget 设置页面的正文。 Null
iconImageAssetPath String 设置将在页面气泡中显示的图标图像路径。 Null
iconColor Color 设置页面气泡图标的颜色。 Null
bubbleBackgroundColor Color 设置页面气泡背景色。 Colors.white / Color(0x88FFFFFF)
textStyle TextStyle 为标题和正文设置TextStyle title: color: Colors.white , fontSize: 50.0 body: color: Colors.white , fontSize: 24.0
titleTextStyle TextStyle 设置标题的TextStyle color: Colors.white , fontSize: 50.0
bodyTextStyle TextStyle 为正文设置TextStyle color: Colors.white , fontSize: 24.0
bubble Widget 为内部气泡设置自定义小部件 null

那咱们就根据使用的小细则定义一个PageViewModel

PageViewModel(
        pageColor: const Color(0xFF03A9F4),
        // iconImageAssetPath: 'assets/air-hostess.png',
        iconColor: Colors.pink,
        bubbleBackgroundColor: Colors.pink,
        // bubble: Image.asset('images/jiche.jpg'),
        body: Text(
          '这是属于你我的孤岛',
        ),
        title: Text(
          'No.1',
        ),
        // titleTextStyle: TextStyle(fontFamily: 'MyFont', color: Colors.white),
        // bodyTextStyle: TextStyle(fontFamily: 'MyFont', color: Colors.white),
        mainImage: Container(
          decoration:
              BoxDecoration(border: Border.all(width: 1, color: Colors.red)),
          child: Image.asset(
            // 图片
            'images/jiche.jpg',
            height: width750,
            width: height1314,
            fit: BoxFit.cover,
            // alignment: Alignment.center, // 居中显示
          ),
        )),

images文件夹咱们又新增了几张图片,用于引导页的展示,图片素材有的是在这儿找的

那现在效果就差不多了,来一起看下

4. 写在最后

很高兴你能耐住性子看到这儿,尤其是在这个快节奏的生活和工作中,我们在今天的这段旅程已经完成了两部分

  • 闪屏
  • 引导页

写到现在已经是凌晨2:35分了

由于这周看了下这个

又出去浪荡了一圈,不过也还没有断更,也算对自己的一个激励吧,所以也请你给个鼓励,评论点赞,当然是感觉还行的话,说不定就出现在下一章节的[读读评论]

我是洋小洋,下回见,本节代码会同步更新仓库 github.com/yayxs/flutt…


--END