Flutter布局錦囊---屏幕頂部提醒

設(shè)計(jì)給的效果如下:

UI布局圖

拿到設(shè)計(jì)后,先把整體拆分成幾個(gè)部分:

  1. “提醒頁(yè)面”,顯示在屏幕上方的文字提醒頁(yè)面,不會(huì)覆蓋原路由頁(yè)面。
  2. “路由導(dǎo)航”,使用Flutter的路由與導(dǎo)航組件來(lái)推(push)提醒頁(yè)面。
  3. “倒計(jì)時(shí)拋”,使用Flutter的倒計(jì)時(shí)組件自動(dòng)拋(pop)提醒頁(yè)面。
  4. “過(guò)渡動(dòng)畫(huà)”,為推(push)和拋(pop)提醒頁(yè)面的過(guò)程添加動(dòng)畫(huà)效果。

然后就可以開(kāi)始進(jìn)行編碼了。

第1步:繪制組件樹(shù)

屏幕頂部提醒的組件樹(shù)

第2步:實(shí)現(xiàn)“提醒頁(yè)面”

屏幕頂部提醒頁(yè)面應(yīng)該只占屏幕一小部分,而且不能遮蓋住原本的路由頁(yè)面,所以你不能使用腳手架(Scaffold)組件,因?yàn)樗鼤?huì)占據(jù)整個(gè)屏幕。直接使用手勢(shì)探測(cè)器(GestureDetector)組件作為頂部提醒組件的根組件,為每一個(gè)子組件都設(shè)置固定的高度,使剩下的屏幕高度都是空白的。

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

// TODO: 第3步:實(shí)現(xiàn)“路由導(dǎo)航”。

/// 自定義的頂部提醒組件。
class TopReminder extends StatefulWidget {
  /// 提醒文本。
  final String reminderText;

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

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

/// 與自定義的頂部提醒組件關(guān)聯(lián)的狀態(tài)子類(lèi)。
class _TopReminderState extends State<TopReminder> {
  // TODO: 第4步:實(shí)現(xiàn)“倒計(jì)時(shí)拋”。

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      child: Column(
        children: <Widget>[
          Container(
            // 雙精度(`double`)類(lèi)的無(wú)窮(`infinity`)常量,最大寬度。
            width: double.infinity,
            height: 85.0,
            color: const Color(0xFFFF6F6F),
            child: Align(
              alignment: Alignment.bottomCenter,
              // 使用材料(`Material`)組件來(lái)避免文本下方的黃色線(xiàn)條。
              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`)屬性,將子組件放在這個(gè)填充內(nèi)。
            padding: EdgeInsets.only(bottom: 7.0),
          ),
          Container(
            height: 3.0,
            color: const Color(0xFF4A4A4A),
          ),
          // 不透明度(`Opacity`)組件,使子組件部分透明。
          Opacity(
            // 不透明度(`opacity`)屬性,縮放子組件的阿爾法通道(`alpha`)值的分?jǐn)?shù)。
            // 不透明度為1.0是完全不透明的,不透明度為0.0是完全透明的(即不可見(jiàn))。
            opacity: 0.5,
            child: Container(
              height: 4.0,
              color: const Color(0xFFCCCCCC),
            ),
          ),
        ],
      ),
      // TODO: 第3步:實(shí)現(xiàn)“路由導(dǎo)航”,點(diǎn)擊提醒頁(yè)面時(shí)返回。
    );
  }
}

第3步:實(shí)現(xiàn)“路由導(dǎo)航”

通過(guò)導(dǎo)航器(Navigator)組件和頁(yè)面路由生成器(PageRouteBuilder)組件,可以實(shí)現(xiàn)打開(kāi)頂部提醒頁(yè)面的方法,通過(guò)這個(gè)openTopReminder方法,你可以在任意路由頁(yè)面調(diào)用頂部提醒頁(yè)面。

// TODO: 第3步:實(shí)現(xiàn)“路由導(dǎo)航”。
/// 打開(kāi)頂部提醒頁(yè)面。
void openTopReminder(context, String reminderText) {
  // 導(dǎo)航器(`Navigator`)組件,用于管理具有堆棧規(guī)則的一組子組件。
  // 許多應(yīng)用程序在其窗口組件層次結(jié)構(gòu)的頂部附近有一個(gè)導(dǎo)航器,以便使用疊加顯示其邏輯歷史記錄,
  // 最近訪(fǎng)問(wèn)過(guò)的頁(yè)面可視化地顯示在舊頁(yè)面之上。使用此模式,
  // 導(dǎo)航器可以通過(guò)在疊加層中移動(dòng)組件來(lái)直觀地從一個(gè)頁(yè)面轉(zhuǎn)換到另一個(gè)頁(yè)面。
  // 類(lèi)似地,導(dǎo)航器可用于通過(guò)將對(duì)話(huà)框窗口組件放置在當(dāng)前頁(yè)面上方來(lái)顯示對(duì)話(huà)框。
  // 導(dǎo)航器(`Navigator`)組件的關(guān)于(`of`)方法,來(lái)自此類(lèi)的最近實(shí)例的狀態(tài),它包含給定的上下文。
  // 導(dǎo)航器(`Navigator`)組件的推(`push`)方法,將給定路徑推送到最緊密包圍給定上下文的導(dǎo)航器。
  Navigator.of(context).push(
    // 頁(yè)面路由生成器(`PageRouteBuilder`)組件,用于根據(jù)回調(diào)定義一次性頁(yè)面路由的實(shí)用程序類(lèi)。
    PageRouteBuilder(
      // 轉(zhuǎn)換完成后路由是否會(huì)遮蓋以前的路由。
      opaque: false,
      // 頁(yè)面構(gòu)建器(`pageBuilder`)屬性,用于構(gòu)建路徑的主要內(nèi)容。
      pageBuilder: (BuildContext context, _, __) {
        return TopReminder(reminderText: reminderText);
      },
      // TODO: 第5步:實(shí)現(xiàn)“過(guò)渡動(dòng)畫(huà)”。
    ),
  );
}

給手勢(shì)探測(cè)器(GestureDetector)組件添加一個(gè)點(diǎn)擊事件,并在回調(diào)函數(shù)中拋棄頂部提醒頁(yè)面,以返回原來(lái)的路由頁(yè)面。

      // TODO: 第3步:實(shí)現(xiàn)“路由導(dǎo)航”,點(diǎn)擊提醒頁(yè)面時(shí)返回。
      onTap: () {
        // TODO: 第4步:實(shí)現(xiàn)“倒計(jì)時(shí)拋”,關(guān)閉倒計(jì)時(shí)。
        Navigator.of(context).pop(true);
      },

第4步:實(shí)現(xiàn)“倒計(jì)時(shí)拋”

除了用戶(hù)手動(dòng)點(diǎn)擊頂部提醒頁(yè)面外,還需要一個(gè)計(jì)時(shí)器來(lái)定時(shí)拋棄頂部提醒頁(yè)面,實(shí)現(xiàn)自動(dòng)返回原來(lái)的路由頁(yè)面。

  // TODO: 第4步:實(shí)現(xiàn)“倒計(jì)時(shí)拋”。
  /// 倒計(jì)時(shí)的計(jì)時(shí)器。
  Timer _timer;

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

  /// 啟動(dòng)倒計(jì)時(shí)的計(jì)時(shí)器。
  _startTimer() {
    _timer = Timer(
      // 持續(xù)時(shí)間參數(shù)。
      Duration(seconds: 2),
      // 回調(diào)函數(shù)參數(shù)。
      () {
        Navigator.of(context).pop(true);
      },
    );
  }

  /// 取消倒計(jì)時(shí)的計(jì)時(shí)器。
  void _cancelTimer() {
    // 計(jì)時(shí)器(`Timer`)組件的取消(`cancel`)方法,取消計(jì)時(shí)器。
    _timer?.cancel();
  }

在手勢(shì)探測(cè)器(GestureDetector)組件的點(diǎn)擊事件中調(diào)用關(guān)閉倒計(jì)時(shí)的_cancelTimer方法,避免用戶(hù)手動(dòng)返回原來(lái)的路由頁(yè)面以后,倒計(jì)時(shí)任務(wù)仍在運(yùn)行,導(dǎo)致應(yīng)用程序拋出異常信息。

        // TODO: 第4步:實(shí)現(xiàn)“倒計(jì)時(shí)拋”,關(guān)閉倒計(jì)時(shí)。
        // 點(diǎn)擊提醒頁(yè)面時(shí)關(guān)閉倒計(jì)時(shí)并返回。
        _cancelTimer();

第5步:實(shí)現(xiàn)“過(guò)渡動(dòng)畫(huà)”

通過(guò)淡出過(guò)渡(FadeTransition)組件使原來(lái)的路由頁(yè)面自然淡出,再通過(guò)滑動(dòng)過(guò)渡(SlideTransition)組件使新的路由頁(yè)面從屏幕頂部上方開(kāi)始,向屏幕下方平滑移動(dòng)。返回原來(lái)的路由頁(yè)面時(shí),也是同樣的效果,不同的是反方向播放動(dòng)畫(huà)。

      // TODO: 第5步:實(shí)現(xiàn)“過(guò)渡動(dòng)畫(huà)”。
      // 轉(zhuǎn)換生成器(`transitionsBuilder`)屬性,用于構(gòu)建路徑的轉(zhuǎn)換。
      transitionsBuilder: (_, Animation<double> animation, __, Widget child) {
        // 淡出過(guò)渡(`FadeTransition`)組件,動(dòng)畫(huà)組件的不透明度。
        // https://docs.flutter.io/flutter/widgets/FadeTransition-class.html
        return FadeTransition(
          // 不透明度(`opacity`)屬性,控制子組件不透明度的動(dòng)畫(huà)。
          opacity: animation,
          // 滑動(dòng)過(guò)渡(`SlideTransition`)組件,動(dòng)畫(huà)組件相對(duì)于其正常位置的位置。
          // https://docs.flutter.io/flutter/widgets/SlideTransition-class.html
          child: SlideTransition(
            // 位置(`position`)屬性,控制子組件位置的動(dòng)畫(huà)。
            // 兩者之間(`Tween`)類(lèi),開(kāi)始值和結(jié)束值之間的線(xiàn)性插值。
            // 偏移(`Offset`)類(lèi),不可變的2D浮點(diǎn)偏移量。
            position: Tween<Offset>(
              // 兩者之間(`Tween`)類(lèi)的開(kāi)始(`begin`)屬性,此變量在動(dòng)畫(huà)開(kāi)頭的值。
              begin: Offset(0.0, -0.3),
              // 兩者之間(`Tween`)類(lèi)的結(jié)束(`end`)屬性,此變量在動(dòng)畫(huà)結(jié)束時(shí)的值。
              end: Offset.zero,
              // 兩者之間(`Tween`)類(lèi)的活躍(`animate`)方法,返回由給定動(dòng)畫(huà)驅(qū)動(dòng)但接受由此對(duì)象確定的值的新動(dòng)畫(huà)。
            ).animate(animation),
            child: child,
          ),
        );
      }

第6步:還原效果

屏幕頂部提醒的還原效果
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文是基于最新的react-navigation^2.9.1來(lái)書(shū)寫(xiě)的。 要感謝掛著鈴鐺的兔看到一篇不錯(cuò)的介紹,這里...
    HT_Jonson閱讀 954評(píng)論 0 52
  • Hibaby兒童攝影親子資訊一直以來(lái)都有不少家長(zhǎng)找到派媽?zhuān)f(shuō)自己的孩子內(nèi)向,沉默寡言,整天見(jiàn)不到笑臉。但是這些家長(zhǎng)...
    嗨貝貝兒童攝影閱讀 262評(píng)論 0 1
  • 思緒飄舞流螢如束流淌一夜的一頁(yè)頁(yè)迷在葉的掌紋里總走不出來(lái)時(shí)路 何處歸去?何為來(lái)處?筆下的一夜夜纏綿一道道葉影一頁(yè)頁(yè)...
    夢(mèng)里秋蟬閱讀 194評(píng)論 0 1
  • 看了一晚空蕩蕩的走廊 這是你過(guò)去一周的夜景 我心很難受 你說(shuō)謝謝我的陪伴 其實(shí),我所做的不是為了挽留 而是真的在乎...
    高國(guó)恒閱讀 854評(píng)論 0 0
  • 凝秋含韻花初笑 飛雪映 化春鳥(niǎo) 休要稱(chēng)道 恐沒(méi)香蕊好 試問(wèn)西風(fēng)多少俏 言難盡 一份嬌 芳魂距我?guī)桌镞b 有情訴 回訊...
    夢(mèng)游的柔兒閱讀 340評(píng)論 0 1

友情鏈接更多精彩內(nèi)容