Flutter布局锦囊---屏幕顶部提醒

设计给的效果如下:

UI布局图

拿到设计后,先把整体拆分成几个部分:

  1. “提醒页面”,显示在屏幕上方的文字提醒页面,不会覆盖原路由页面。
  2. “路由导航”,使用Flutter的路由与导航组件来推(push)提醒页面。
  3. “倒计时抛”,使用Flutter的倒计时组件自动抛(pop)提醒页面。
  4. “过渡动画”,为推(push)和抛(pop)提醒页面的过程添加动画效果。

然后就可以开始进行编码了。

第1步:绘制组件树

屏幕顶部提醒的组件树

第2步:实现“提醒页面”

屏幕顶部提醒页面应该只占屏幕一小部分,而且不能遮盖住原本的路由页面,所以你不能使用脚手架(Scaffold)组件,因为它会占据整个屏幕。直接使用手势探测器(GestureDetector)组件作为顶部提醒组件的根组件,为每一个子组件都设置固定的高度,使剩下的屏幕高度都是空白的。

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

// TODO: 第3步:实现“路由导航”。

/// 自定义的顶部提醒组件。
class TopReminder extends StatefulWidget {
  /// 提醒文本。
  final String reminderText;

  TopReminder({
    @required
    this.reminderText,
  });

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

/// 与自定义的顶部提醒组件关联的状态子类。
class _TopReminderState extends State<TopReminder> {
  // TODO: 第4步:实现“倒计时抛”。

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Column(
        children: <Widget>[
          Container(
            // 双精度(`double`)类的无穷(`infinity`)常量,最大宽度。
            width: double.infinity,
            height: 85.0,
            color: const Color(0xFFFF6F6F),
            child: Align(
              alignment: Alignment.bottomCenter,
              // 使用材料(`Material`)组件来避免文本下方的黄色线条。
              child: Material(
                color: const Color(0xFFFF6F6F),
                child: Text(
                  widget.reminderText,
                  overflow: TextOverflow.ellipsis,
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 18.0,
                    color: const Color(0xFF282828),
                  ),
                ),
              ),
            ),
            // 容器(`Container`)组件的填充(`padding`)属性,将子组件放在这个填充内。
            padding: EdgeInsets.only(bottom: 7.0),
          ),
          Container(
            height: 3.0,
            color: const Color(0xFF4A4A4A),
          ),
          // 不透明度(`Opacity`)组件,使子组件部分透明。
          Opacity(
            // 不透明度(`opacity`)属性,缩放子组件的阿尔法通道(`alpha`)值的分数。
            // 不透明度为1.0是完全不透明的,不透明度为0.0是完全透明的(即不可见)。
            opacity: 0.5,
            child: Container(
              height: 4.0,
              color: const Color(0xFFCCCCCC),
            ),
          ),
        ],
      ),
      // TODO: 第3步:实现“路由导航”,点击提醒页面时返回。
    );
  }
}

第3步:实现“路由导航”

通过导航器(Navigator)组件和页面路由生成器(PageRouteBuilder)组件,可以实现打开顶部提醒页面的方法,通过这个openTopReminder方法,你可以在任意路由页面调用顶部提醒页面。

// TODO: 第3步:实现“路由导航”。
/// 打开顶部提醒页面。
void openTopReminder(context, String reminderText) {
  // 导航器(`Navigator`)组件,用于管理具有堆栈规则的一组子组件。
  // 许多应用程序在其窗口组件层次结构的顶部附近有一个导航器,以便使用叠加显示其逻辑历史记录,
  // 最近访问过的页面可视化地显示在旧页面之上。使用此模式,
  // 导航器可以通过在叠加层中移动组件来直观地从一个页面转换到另一个页面。
  // 类似地,导航器可用于通过将对话框窗口组件放置在当前页面上方来显示对话框。
  // 导航器(`Navigator`)组件的关于(`of`)方法,来自此类的最近实例的状态,它包含给定的上下文。
  // 导航器(`Navigator`)组件的推(`push`)方法,将给定路径推送到最紧密包围给定上下文的导航器。
  Navigator.of(context).push(
    // 页面路由生成器(`PageRouteBuilder`)组件,用于根据回调定义一次性页面路由的实用程序类。
    PageRouteBuilder(
      // 转换完成后路由是否会遮盖以前的路由。
      opaque: false,
      // 页面构建器(`pageBuilder`)属性,用于构建路径的主要内容。
      pageBuilder: (BuildContext context, _, __) {
        return TopReminder(reminderText: reminderText);
      },
      // TODO: 第5步:实现“过渡动画”。
    ),
  );
}

给手势探测器(GestureDetector)组件添加一个点击事件,并在回调函数中抛弃顶部提醒页面,以返回原来的路由页面。

      // TODO: 第3步:实现“路由导航”,点击提醒页面时返回。
      onTap: () {
        // TODO: 第4步:实现“倒计时抛”,关闭倒计时。
        Navigator.of(context).pop(true);
      },

第4步:实现“倒计时抛”

除了用户手动点击顶部提醒页面外,还需要一个计时器来定时抛弃顶部提醒页面,实现自动返回原来的路由页面。

  // TODO: 第4步:实现“倒计时抛”。
  /// 倒计时的计时器。
  Timer _timer;

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

  /// 启动倒计时的计时器。
  _startTimer() {
    _timer = Timer(
      // 持续时间参数。
      Duration(seconds: 2),
      // 回调函数参数。
      () {
        Navigator.of(context).pop(true);
      },
    );
  }

  /// 取消倒计时的计时器。
  void _cancelTimer() {
    // 计时器(`Timer`)组件的取消(`cancel`)方法,取消计时器。
    _timer?.cancel();
  }

在手势探测器(GestureDetector)组件的点击事件中调用关闭倒计时的_cancelTimer方法,避免用户手动返回原来的路由页面以后,倒计时任务仍在运行,导致应用程序抛出异常信息。

        // TODO: 第4步:实现“倒计时抛”,关闭倒计时。
        // 点击提醒页面时关闭倒计时并返回。
        _cancelTimer();

第5步:实现“过渡动画”

通过淡出过渡(FadeTransition)组件使原来的路由页面自然淡出,再通过滑动过渡(SlideTransition)组件使新的路由页面从屏幕顶部上方开始,向屏幕下方平滑移动。返回原来的路由页面时,也是同样的效果,不同的是反方向播放动画。

      // TODO: 第5步:实现“过渡动画”。
      // 转换生成器(`transitionsBuilder`)属性,用于构建路径的转换。
      transitionsBuilder: (_, Animation<double> animation, __, Widget child) {
        // 淡出过渡(`FadeTransition`)组件,动画组件的不透明度。
        // https://docs.flutter.io/flutter/widgets/FadeTransition-class.html
        return FadeTransition(
          // 不透明度(`opacity`)属性,控制子组件不透明度的动画。
          opacity: animation,
          // 滑动过渡(`SlideTransition`)组件,动画组件相对于其正常位置的位置。
          // https://docs.flutter.io/flutter/widgets/SlideTransition-class.html
          child: SlideTransition(
            // 位置(`position`)属性,控制子组件位置的动画。
            // 两者之间(`Tween`)类,开始值和结束值之间的线性插值。
            // 偏移(`Offset`)类,不可变的2D浮点偏移量。
            position: Tween<Offset>(
              // 两者之间(`Tween`)类的开始(`begin`)属性,此变量在动画开头的值。
              begin: Offset(0.0, -0.3),
              // 两者之间(`Tween`)类的结束(`end`)属性,此变量在动画结束时的值。
              end: Offset.zero,
              // 两者之间(`Tween`)类的活跃(`animate`)方法,返回由给定动画驱动但接受由此对象确定的值的新动画。
            ).animate(animation),
            child: child,
          ),
        );
      }

第6步:还原效果

屏幕顶部提醒的还原效果
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容

  • 本文是基于最新的react-navigation^2.9.1来书写的。 要感谢挂着铃铛的兔看到一篇不错的介绍,这里...
    HT_Jonson阅读 891评论 0 52
  • Hibaby儿童摄影亲子资讯一直以来都有不少家长找到派妈,说自己的孩子内向,沉默寡言,整天见不到笑脸。但是这些家长...
    嗨贝贝儿童摄影阅读 233评论 0 1
  • 思绪飘舞流萤如束流淌一夜的一页页迷在叶的掌纹里总走不出来时路 何处归去?何为来处?笔下的一夜夜缠绵一道道叶影一页页...
    梦里秋蝉阅读 135评论 0 1
  • 看了一晚空荡荡的走廊 这是你过去一周的夜景 我心很难受 你说谢谢我的陪伴 其实,我所做的不是为了挽留 而是真的在乎...
    高国恒阅读 658评论 0 0
  • 凝秋含韵花初笑 飞雪映 化春鸟 休要称道 恐没香蕊好 试问西风多少俏 言难尽 一份娇 芳魂距我几里遥 有情诉 回讯...
    梦游的柔儿阅读 313评论 0 1