美文网首页Flutter
Flutter 玩转微信——闪屏页妙用

Flutter 玩转微信——闪屏页妙用

作者: 奶盖ww | 来源:发表于2019-12-26 09:49 被阅读0次

    概述

    • 众所周知,一个健全的App,通常都会有一个SplashPage页面,且该页面一般用于应用(APP)启动时,当然其存在的主要目的是承载:启动页引导页广告页等待页等业务场景。笔者认为,与其说是闪屏页,倒不如叫中转页,怎么个中转法,还请听笔者一一到来...

    • 这里笔者借助以Flutter实现微信App登录的逻辑,以点带面来讲讲SplashPage页面产生的原因和作用,SplashPage页面如何实现上面👆提到的几种常用的业务场景。希望大家能够举一反三,能够更好的妙用SplashPage页,从而更好的实现所需功能,以及提高用户的体验。

    • 源码地址:flutter_wechat

    场景

    启动页 引导页

    |

    |
    | 广告页 | 主页 |
    |

    |

    |

    由来

    上面提到过,闪屏页只是外界一种通俗的说法,但其本质就是用来中转转场的。比如现实场景中,程序一旦启动,我们可能需要:读取本地的用户数据读取文件存储的(广告)图片请求token是否失效请求一些公有数据,内存缓存... ,众所周知,这些操作场景都是比较耗时的,且一般我们都是异步去处理的,以及有时候我们必须等这些耗时操作返回数据后,才能进行下一步操作。
    当然,闪屏页就是为了解决耗时异步的场景而闪亮登场的。其目的就是:利用闪屏页,友好的来等待异步耗时数据的返回,根据数据返回丝滑的过渡到目标页面,从而增大用户体验。
    这里笔者就拿程序一旦启动,读取本地用户信息(耗时),根据用户信息有无,来显示不同界面(主页或登录)的常见场景,进一步来说明闪屏页的妙用。

    伪代码如下:

    // 获取用户数据 耗时操作 异步请求
    await final userInfo = _fetchUserInfo();
    if (userInfo != null) {
      // 有用户数据,跳转到主页
    } else {
      // 没有用户数据,跳转到登录页
    }
    
    

    方案一:main函数处理
    伪代码如下:

    void main() async {
      // 获取用户数据
      await final userInfo = _fetchUserInfo();
      if (userInfo != null) {
        // 有用户数据,跳转到主页
      } else {
        // 没有用户数据,跳转到登录页
      }
    }
    
    

    优点:无需增加闪屏页(中转页),代码逻辑比较清晰
    缺点:只适合耗时比较短的异步请求(100ms之内),否则程序一启动,会有肉眼可见的卡顿,影响用户体验。

    方案二:闪屏页处理
    程序一启动,立即切换到闪屏页,闪屏页初始化的时候异步获取用户数据,且闪屏页默认展示跟iOS或Android一致的启动页,从而迷惑用户认为App正常启动的错觉,从而无形之中提高了用户的体验。
    一旦耗时的数据异步返回了,然后再去丝滑的切换页面。
    代码实现如下:

    // SplashPage.dart
    class _SplashPageState extends State<SplashPage>{
      @override
      void initState() {
        super.initState();
        // 初始化
        initAsync();
      }
    
      // 异步初始化
      void initAsync() async {
        // 获取用户数据
        await final userInfo = _fetchUserInfo();
        if (userInfo != null) {
          // 有用户数据,跳转到主页
        } else {
          // 没有用户数据,跳转到登录页
        }
      }
    
      @override
      Widget build(BuildContext context) {
        // 返回启动页
        return LaunchImage()
      }
    }
    
    

    优点:极大的增强了用户体验,且拓展性强,以此可以衍生出引导页广告页...等常见业务场景,下面会一一说到。
    综上所述,侧面验证了,闪屏页一般是用来友好的等待异步耗时的数据返回,根据数据返回丝滑的过渡到目标页面,从而极大的增强用户体验而产生。

    用途

    闪屏页在现实场景中,使用是非常广泛的,笔者相信一款正常的App都会使用到闪屏页,且大多数用于程序启动时启动页引导页广告页等待页等业务场景。
    这里笔者借用实现微信App启动的逻辑,来阐述一下闪屏页的用途,希望大家能够举一反三,能够将其用于现实的开发场景中去。

    微信启动逻辑图

    上面就是笔者整理的微信登陆逻辑,大家可以打开你手机上的微信App,逐个验证各个逻辑。当然微信是没有广告页的,笔者这里增加广告逻辑只是为了满足业界通用App的逻辑罢了,微信的做法是:v1 == v2 => 根据account和userInfo判断 => 切换页面,可见,微信的登陆比上面逻辑图更简单,这里笔者就不一一赘述了。

    其次,笔者相信,上面的逻辑图,应该能满足业界80%以上的app启动逻辑,大家如果有任何疑问或者有更好的解决方案,欢迎留言交流,谢谢。

    最后,相信有了逻辑图,大家写起代码也比较胸有成竹了,也希望大家在写代码之前,先写好流程图,避免像无头苍蝇一样,毫无目标性。记住:在错误的道路上,停止就是前进!

    代码

    /// 闪屏跳转模式
    enum MHSplashSkipMode {
      newFeature, // 新特性(引导页)
      login, // 登陆
      currentLogin, // 账号登陆
      homePage, // 主页
      ad, // 广告页
    }
    
    /// 闪屏界面主要用来中转(新特性界面、登陆界面、主页面)
    class SplashPage extends StatefulWidget {
      SplashPage({Key key}) : super(key: key);
      _SplashPageState createState() => _SplashPageState();
    }
    
    class _SplashPageState extends State<SplashPage> {
      /// 跳转方式
      MHSplashSkipMode _skipMode;
    
      /// 定时器相关
      TimerUtil _timerUtil;
    
      /// 计数
      int _count = 5;
    
      /// 点击是否高亮
      bool _highlight = false;
    
      @override
      void dispose() {
        super.dispose();
        print('🔥 Splash Page is Over 👉');
        // 记得中dispose里面把timer cancel。
        if (_timerUtil != null) _timerUtil.cancel();
      }
    
      @override
      void initState() {
        super.initState();
        // 监听部件渲染完
        /// widget渲染监听。
        WidgetUtil widgetUtil = new WidgetUtil();
        widgetUtil.asyncPrepares(true, (_) async {
          // widget渲染完成。
          // App启动时读取Sp数据,需要异步等待Sp初始化完成。必须保证它 优先初始化。
          await SpUtil.getInstance();
    
          // 获取一下通讯录数据,理论上是在跳转到主页时去请求
          ContactsService.sharedInstance;
    
          // 读取一下全球手机区号编码
          ZoneCodeService.sharedInstance;
    
          /// 获取App信息
          PackageInfo packageInfo = await PackageInfo.fromPlatform();
          // String appName = packageInfo.appName;
          // String packageName = packageInfo.packageName;
          String version = packageInfo.version;
          String buildNumber = packageInfo.buildNumber;
    
          // 拼接app version
          final String appVersion = version + '+' + buildNumber;
    
          // 获取缓存的版本号
          final String cacheVersion = SpUtil.getString(CacheKey.appVersionKey);
    
          // 获取用户信息
          if (appVersion != cacheVersion) {
            // 保存版本
            SpUtil.putString(CacheKey.appVersionKey, appVersion);
            // 更新页面,切换为新特性页面
            setState(() {
              _skipMode = MHSplashSkipMode.newFeature;
            });
          } else {
            // _switchRootView();
            setState(() {
              _skipMode = MHSplashSkipMode.ad;
            });
            // 配置定时器
            _configureCountDown();
          }
        });
      }
    
      // 切换rootView
      void _switchRootView() {
        // 取出登陆账号
        final String rawLogin = AccountService.sharedInstance.rawLogin;
        // 取出用户
        final User currentUser = AccountService.sharedInstance.currentUser;
        // 跳转路径
        String skipPath;
        // 跳转模式
        MHSplashSkipMode skipMode;
        if (Util.isNotEmptyString(rawLogin) && currentUser != null) {
          // 有登陆账号 + 有用户数据 跳转到 主页
          skipMode = MHSplashSkipMode.homePage;
          skipPath = Routers.homePage;
        } else if (currentUser != null) {
          // 没有登陆账号 + 有用户数据 跳转到当前登陆
          skipMode = MHSplashSkipMode.currentLogin;
          skipPath = LoginRouter.currentLoginPage;
        } else {
          // 没有登陆账号 + 没有用户数据 跳转到登陆
          skipMode = MHSplashSkipMode.login;
          skipPath = LoginRouter.loginPage;
        }
        // 这里无需更新 页面 直接跳转即可
        _skipMode = skipMode;
    
        // 跳转对应的主页
        NavigatorUtils.push(context, skipPath,
            clearStack: true, transition: TransitionType.fadeIn);
      }
    
      /// 配置倒计时
      void _configureCountDown() {
        _timerUtil = TimerUtil(mTotalTime: 5000);
        _timerUtil.setOnTimerTickCallback((int tick) {
          double _tick = tick / 1000;
          if (_tick == 0) {
            // 切换到主页面
            _switchRootView();
          } else {
            setState(() {
              _count = _tick.toInt();
            });
          }
        });
        _timerUtil.startCountDown();
      }
    
      @override
      Widget build(BuildContext context) {
        /// 配置屏幕适配的  flutter_screenutil 和  flustars 设计稿的宽度和高度(单位px)
        /// Set the fit size (fill in the screen size of the device in the design) If the design is based on the size of the iPhone6 ​​(iPhone6 ​​750*1334)
        // 配置设计图尺寸,iphone 7 plus 1242.0 x 2208.0
        final double designW = 1242.0;
        final double designH = 2208.0;
    
        FlutterScreenUtil.ScreenUtil.instance =
            FlutterScreenUtil.ScreenUtil(width: designW, height: designH)
              ..init(context);
        setDesignWHD(designW, designH, density: 3);
    
        /// If you use a dependent context-free method to obtain screen parameters and adaptions, you need to call this method.
        MediaQuery.of(context);
        Widget child;
        if (_skipMode == MHSplashSkipMode.newFeature) {
          // 引导页
          child = _buildNewFeatureWidget();
        } else if (_skipMode == MHSplashSkipMode.ad) {
          // 广告页
          child = _buildAdWidget();
        } else {
          // 启动页
          child = _buildDefaultLaunchImage();
        }
        return Material(child: child);
      }
    
      /// 默认情况是一个启动页 1200x530
      /// https://game.gtimg.cn/images/yxzj/img201606/heroimg/121/121-bigskin-4.jpg
      Widget _buildDefaultLaunchImage() {
        return Container(
          width: double.maxFinite,
          height: double.maxFinite,
          decoration: BoxDecoration(
            // 这里设置颜色 跟启动页一致的背景色,以免发生白屏闪烁
            color: Color.fromRGBO(0, 10, 24, 1),
            image: DecorationImage(
              // 注意:启动页 别搞太大 以免加载慢
              image: AssetImage(Constant.assetsImages + 'LaunchImage.png'),
              fit: BoxFit.cover,
            ),
          ),
        );
      }
    
      /// 新特性界面
      Widget _buildNewFeatureWidget() {
        return Swiper(
          itemCount: 3,
          loop: false,
          itemBuilder: (_, index) {
            final String name =
                Constant.assetsImagesNewFeature + 'intro_page_${index + 1}.png';
            Widget widget = Image.asset(
              name,
              fit: BoxFit.cover,
              width: double.infinity,
              height: double.infinity,
            );
            if (index == 2) {
              return Stack(
                children: <Widget>[
                  widget,
                  Positioned(
                    child: InkWell(
                      child: Image.asset(
                        Constant.assetsImagesNewFeature + 'skip_btn.png',
                        width: 175.0,
                        height: 55.0,
                      ),
                      onTap: _switchRootView,
                      highlightColor: Colors.transparent,
                      splashColor: Colors.transparent,
                      focusColor: Colors.transparent,
                    ),
                    left: (ScreenUtil.getInstance().screenWidth - 175) * 0.5,
                    bottom: 55.0,
                    width: 175.0,
                    height: 55.0,
                  ),
                ],
              );
            } else {
              return widget;
            }
          },
        );
      }
    
      /// 广告页
      Widget _buildAdWidget() {
        return Container(
          child: _buildAdChildWidget(),
          width: double.infinity,
          height: double.infinity,
          decoration: BoxDecoration(
            // 这里设置颜色 跟背景一致的背景色,以免发生白屏闪烁
            color: Color.fromRGBO(21, 5, 27, 1),
            image: DecorationImage(
              image: AssetImage(Constant.assetsImagesBg + 'SkyBg01_320x490.png'),
              fit: BoxFit.cover,
            ),
          ),
        );
      }
    
      Widget _buildAdChildWidget() {
        final double horizontal =
            FlutterScreenUtil.ScreenUtil.getInstance().setWidth(30.0);
        final double vertical =
            FlutterScreenUtil.ScreenUtil.getInstance().setHeight(9.0);
        final double fontSize =
            FlutterScreenUtil.ScreenUtil.getInstance().setSp(42.0);
        final lineHeight =
            FlutterScreenUtil.ScreenUtil.getInstance().setHeight(20.0 * 3 / 14.0);
        final radius = FlutterScreenUtil.ScreenUtil.getInstance().setWidth(108.0);
        return Stack(
          children: <Widget>[
            Swiper(
              onTap: (idx) {
                print('onTap $idx');
                // 跳转到Web
              },
              itemCount: 4,
              autoplayDelay: 1500,
              loop: true,
              autoplay: true,
              itemBuilder: (_, index) {
                return Center(
                  child: Image.asset(
                    Constant.assetsImagesAds + '121-bigskin-${index + 1}.jpg',
                    fit: BoxFit.cover,
                  ),
                );
              },
            ),
            Positioned(
              top: FlutterScreenUtil.ScreenUtil.getInstance().setWidth(60.0),
              right: FlutterScreenUtil.ScreenUtil.getInstance().setWidth(60.0),
              child: InkWell(
                onTap: () {
                  if (_timerUtil != null) {
                    _timerUtil.cancel();
                  }
                  _switchRootView();
                },
                onHighlightChanged: (highlight) {
                  setState(() {
                    _highlight = highlight;
                  });
                },
                child: Container(
                  padding: EdgeInsets.symmetric(
                      horizontal: horizontal, vertical: vertical),
                  alignment: Alignment.center,
                  decoration: BoxDecoration(
                    color: _highlight ? Colors.white30 : Colors.white10,
                    border: Border.all(color: Colors.white, width: 0),
                    borderRadius: BorderRadius.all(Radius.circular(radius)),
                  ),
                  child: Text(
                    '跳过 $_count',
                    textAlign: TextAlign.center,
                    style: TextStyle(
                        color: Colors.white,
                        fontSize: fontSize,
                        height: lineHeight),
                  ),
                ),
              ),
            )
          ],
        );
      }
    }
    
    

    总结

    闪屏页的功能虽然很简单,但是作用却非常大,希望大家通过阅读本篇文章,能够更好地认识闪屏页,了解他的由来,知道他的用途,并将其优点运用到实际开发中去,能够实实在在的解决现实中的问题。

    • 由来:用来友好的等待异步耗时的数据返回,再根据数据返回丝滑的过渡到目标页面,从而极大的增强用户体验而产生。
    • 用途:一般是用在App启动时,承载启动页、引导页、广告页、等待页等业务场景。

    期待

    1. 文章若对您有些许帮助,请给个喜欢❤️,毕竟码字不易;若对您没啥帮助,请给点建议💗,切记学无止境。
    2. 针对文章所述内容,阅读期间任何疑问;请在文章底部评论指出,我会火速解决和修正问题。
    3. GitHub地址:https://github.com/CoderMikeHe
    4. 源码地址:flutter_wechat

    参考链接

    作者:CoderMikeHe
    链接:https://www.jianshu.com/p/e2dcd0e8e04d

    相关文章

      网友评论

        本文标题:Flutter 玩转微信——闪屏页妙用

        本文链接:https://www.haomeiwen.com/subject/lhlkoctx.html