Flutter Loading組件的自定義

什么是Overlay ,OverlayEntry烈和,OverlayState

Overlay: 一個(gè)可以獨(dú)立管理的Stack让蕾。 通過(guò)它讓W(xué)idget浮在其他的Widget之上,從而實(shí)現(xiàn)懸浮窗效果结蟋。主要作用是通過(guò)內(nèi)部的Stack來(lái)管理和疊放各個(gè)組件層脯倚,包括需要置頂?shù)膽腋〗M件已經(jīng)各中頁(yè)面至今的切換。
OverlayEntry: 可以放在Overlay上的組件嵌屎。OverlayEntry是通過(guò)build創(chuàng)建widget的推正,可以同過(guò)OverlayEntry的markNeedsBuild方法在下一幀重繪。
OverlayState:通過(guò) Overlay.of(context).insert和 Overlay.of(context).insert()和OverlayEntry的remove()將OverlayEntry從Overlay上插入和移除宝惰。

Overlay一般不需要開發(fā)者手動(dòng)創(chuàng)建植榕,因?yàn)橐话鉌lutter應(yīng)用常見 的根 MaterialApp,WidgetsApp都會(huì)默認(rèn)創(chuàng)建一個(gè)Navigator尼夺,而Navigator內(nèi)部又會(huì)創(chuàng)建一個(gè)Overlay組件内贮。
關(guān)系:
MaterialApp -> WidgetsApp -> Navigator -> Overlay

簡(jiǎn)單的使用

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData.light(),
      home: const Home(),
    );
  }
}

class Home extends StatefulWidget {
  const Home({Key? key}) : super(key: key);

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  OverlayEntry? _entry;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('OverlayEntry的使用'),
      ),
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            TextButton(
                onPressed: () {
                  _entry = OverlayEntry(builder: (cxt) {
                    //可以看出內(nèi)部使用的是Stack包裹的,可以使用Center將Container居中
                    return Positioned(
                        right: 100,
                        top: 100,
                        child: Container(
                          width: 100,
                          height: 100,
                          color: Colors.deepOrange,
                        ));
                  });
                  Overlay.of(context)?.insert(_entry!);
                },
                style: ButtonStyle(
                    foregroundColor: ButtonStyleButton.allOrNull(Colors.white),
                    backgroundColor: ButtonStyleButton.allOrNull(Colors.green)),
                child: const Text('添加')),
            TextButton(
                onPressed: () {
                  _entry?.remove();
                },
                style: ButtonStyle(
                    foregroundColor: ButtonStyleButton.allOrNull(Colors.white),
                    backgroundColor: ButtonStyleButton.allOrNull(Colors.red)),
                child: const Text('移除')),
          ],
        ),
      ),
    );
  }
}
OverlayEntry.png

Loading組件的實(shí)現(xiàn)和分析

首先介紹一下MaterialApp和WidgetsApp的builder方法,我們可以通過(guò)這個(gè)方法創(chuàng)建屬于自己的Overlay包裹住每個(gè)頁(yè)面。
代碼中的注釋介紹

A builder for inserting widgets above the [Navigator] 用于在 [Navigator] 上方插入小部件的構(gòu)建器

定義 一個(gè)OverlayEntry

//在Flutter3.0之后SchedulerBinding.instance就不是一個(gè)可空類型了,為了兼容3.0之前使用此方法
//https://docs.flutter.dev/development/tools/sdk/release-notes/release-notes-3.0.0
T? _ambiguate<T>(T? value) => value;

class CustomOverlayEntry extends OverlayEntry{
  @override
  CustomOverlayEntry({
    required WidgetBuilder builder,
  }) : super(builder: builder);

  @override
  void markNeedsBuild() {
    //為了保證不在build階段刷新OverlayEntry,具體的可閱讀一下這篇文章
    // https://book.flutterchina.club/chapter14/flutter_app_startup.html
    if (_ambiguate(SchedulerBinding.instance)!.schedulerPhase ==
        SchedulerPhase.persistentCallbacks) {
      _ambiguate(SchedulerBinding.instance)!.addPostFrameCallback((_) {
        super.markNeedsBuild();
      });
    } else {
      super.markNeedsBuild();
    }
  }
}

創(chuàng)建一個(gè)Overlay

創(chuàng)建一個(gè)Overlay汞斧,通過(guò)MaterialApp的builder 方法,使能包裹住頁(yè)面夜郁。

class CustomLoading extends StatefulWidget {
  final Widget? child;

  const CustomLoading({Key? key, required this.child}) : super(key: key);

  @override
  State<CustomLoading> createState() => _CustomLoadingState();
}

class _CustomLoadingState extends State<CustomLoading> {
  late CustomOverlayEntry _entry;

  @override
  void initState() {
    super.initState();
    _entry = CustomOverlayEntry(
        builder: (_) => LoadingManager.instance.widget ?? Container());
    LoadingManager.instance.entry = _entry;
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Overlay(
        initialEntries: [
          CustomOverlayEntry(
            builder: (BuildContext context) {
              if (widget.child != null) {
                return widget.child!;
              } else {
                return Container();
              }
            },
          ),
          _entry,
        ],
      ),
    );
  }
}

創(chuàng)建一個(gè)單例用來(lái)管理這個(gè)Overlay

class LoadingManager {
  static final LoadingManager instance = LoadingManager._internal();

  factory LoadingManager() {
    return instance;
  }

  LoadingManager._internal();

  CustomOverlayEntry? entry;
  Widget? widget;

  static TransitionBuilder builder({
    TransitionBuilder? builder,
  }) {
    return (BuildContext context, Widget? child) {
      if (builder != null) {
        return builder(context, CustomLoading(child: child));
      } else {
        return CustomLoading(child: child);
      }
    };
  }

  static void show() {
    instance.widget = Center(
      child: Container(
        color: Colors.amberAccent,
        width: 60,
        height: 60,
        padding: EdgeInsets.all(10),
        child: const CircularProgressIndicator(
          valueColor: AlwaysStoppedAnimation(Colors.green),
        ),
      ),
    );
    instance.entry?.markNeedsBuild();
  }

  static void dismiss() {
    instance.widget = null;
    instance.entry?.markNeedsBuild();
  }
}

使用

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

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData.light(),
      builder: LoadingManager.builder(),
      home: const Home(),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('自定義Loading'),
      ),
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            TextButton(
                onPressed: () {
                  LoadingManager.show();
                },
                style: ButtonStyle(
                    foregroundColor: ButtonStyleButton.allOrNull(Colors.white),
                    backgroundColor: ButtonStyleButton.allOrNull(Colors.green)),
                child: const Text('show')),
            TextButton(
                onPressed: () {
                  LoadingManager.dismiss();
                },
                style: ButtonStyle(
                    foregroundColor: ButtonStyleButton.allOrNull(Colors.white),
                    backgroundColor: ButtonStyleButton.allOrNull(Colors.red)),
                child: const Text('dismiss')),
          ],
        ),
      ),
    );
  }
}
自定義Loading.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市粘勒,隨后出現(xiàn)的幾起案子竞端,更是在濱河造成了極大的恐慌,老刑警劉巖庙睡,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件事富,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡乘陪,警方通過(guò)查閱死者的電腦和手機(jī)统台,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)啡邑,“玉大人贱勃,你說(shuō)我怎么就攤上這事。” “怎么了贵扰?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵仇穗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我戚绕,道長(zhǎng)纹坐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任舞丛,我火速辦了婚禮耘子,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘球切。我一直安慰自己谷誓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布欧聘。 她就那樣靜靜地躺著片林,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怀骤。 梳的紋絲不亂的頭發(fā)上费封,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音蒋伦,去河邊找鬼弓摘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛痕届,可吹牛的內(nèi)容都是我干的韧献。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼研叫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锤窑!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起嚷炉,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤渊啰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后申屹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绘证,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年哗讥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嚷那。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡杆煞,死狀恐怖魏宽,靈堂內(nèi)的尸體忽然破棺而出腐泻,到底是詐尸還是另有隱情,我是刑警寧澤湖员,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布贫悄,位于F島的核電站瑞驱,受9級(jí)特大地震影響娘摔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜唤反,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一凳寺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧彤侍,春花似錦肠缨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至名斟,卻和暖如春脑慧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背砰盐。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工闷袒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人岩梳。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓囊骤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親冀值。 傳聞我的和親對(duì)象是個(gè)殘疾皇子也物,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容