Flutter 如何创建Toast或Notifications?叠加的概念

如何在Flutter的任何屏幕/页面上显示内容?

难度:中级

前言

最近我写了一些代码来处理WebSockets,我需要在任何屏幕/页面的顶部显示一个图标,以便在服务器发送通知时通知用户。

我尝试使用PopupRouteshowDialog ...但是永远无法获得我想要实现的目标。

当我们使用路由时,整个屏幕被覆盖,并且用户无法继续使用当前页面“ 正常 ” 工作因为后者被另一个页面替换(覆盖)。因此,我继续我的调查,发现OverlayOverlayEntry的概念。

通过阅读Flutter的源代码,我发现Flutter使用OverlayEntry来显示drag avatar(请参阅Draggable)。因此,我明白这就是我要找的东西。

覆盖

Flutter文档说:“ * overlay是一组可以独立管理的条目。Overlays让独立的widgets“浮动”在其他widgets之上......* ”。

用我自己很简单的话说。

overlay不外乎是一个layer (StatefulWidget),在所有widget之上,它包含一个Stack,这里我们可以添加任何小部件

这些小部件称为OverlayEntry

OverlayState

Overlay是一个StatefulWidgetOverlayState是Overlay实例的State,负责渲染。

OverlayEntry

OverlayEntry是一个Widget,我们可以把他插入到OverlayOverlayEntry一次最多只能插入到一个Overlay

由于Overlay使用Stack布局,因此overlay窗口可以使用PositionedAnimatedPositioned将自己定位在叠加层中。

嗯,这正是我需要的:能够在屏幕上的任何地方显示我的通知图标

让我们直接跳转到一些代码

以下类在坐标(50.0,50.0)处显示一个Icon,并在2秒后将其删除。

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

class ShowNotificationIcon {

    void show(BuildContext context) async {
        OverlayState overlayState = Overlay.of(context);
        OverlayEntry overlayEntry = new OverlayEntry(builder: _build);

        overlayState.insert(overlayEntry);

        await new Future.delayed(const Duration(seconds: 2));

        overlayEntry.remove();
    }

    Widget _build(BuildContext context){
      return new Positioned(
        top: 50.0,
        left: 50.0,
        child: new Material(
            color: Colors.transparent,
            child: new Icon(Icons.warning, color: Colors.purple),
        ),
      );
    }
}

如您所见,为了能够显示图标,我们需要提供Context。为什么?

官方文档没有解释这一点,但是,看一下源代码,我们看到为每个Route创建了一个Overlay,因此它属于一个Context树(参见我关于上下文概念的文章)。因此,有必要找到对应于特定ContextOverlayState第7行)。

就是这个! 从这个例子中,我们可以推导出任何类型的叠加内容和行为。

为了说明这一点,让我们构建一种闪烁的Toast,它在屏幕上的某个位置显示一个Widget,并在一定时间后消失。

示例:闪烁Toast

第一类是前一个例子的概括。它允许提供外部Widget构造函数。

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

class BlinkingToast {
    bool _isVisible = false;

    ///
    /// BuildContext context: the context from which we need to retrieve the Overlay
    /// WidgetBuilder externalBuilder: (compulsory) external routine that builds the Widget to be displayed
    /// Duration duration: (optional) duration after which the Widget will be removed
    /// Offset position: (optional) position where you want to show the Widget
    ///
    void show({
        @required BuildContext context,
        @required WidgetBuilder externalBuilder, 
        Duration duration = const Duration(seconds: 2),
        Offset position = Offset.zero,
        }) async {

        // Prevent from showing multiple Widgets at the same time
        if (_isVisible){
            return;
        }

        _isVisible = true;

        OverlayState overlayState = Overlay.of(context);
        OverlayEntry overlayEntry = new OverlayEntry(
            builder: (BuildContext context) => new BlinkingToastWidget(
                widget: externalBuilder(context),
                position: position,
            ),
        );
        overlayState.insert(overlayEntry);

        await new Future.delayed(duration);

        overlayEntry.remove();

        _isVisible = false;
    }
}

第二个类在屏幕上的某个位置显示Widget并使其闪烁。

有关动画概念的进一步说明,请参阅我关于此主题的文章。

class BlinkingToastWidget extends StatefulWidget {
    BlinkingToastWidget({
        Key key,
        @required this.widget,
        @required this.position,
    }): super(key: key);

    final Widget widget;
    final Offset position;

    @override
    _BlinkingToastWidgetState createState() => new _BlinkingToastWidgetState();
}

class _BlinkingToastWidgetState extends State
    with SingleTickerProviderStateMixin {

AnimationController _controller;
  Animation _animation;

  @override
  void initState() {
    super.initState();
    _controller = new AnimationController(
        duration: const Duration(milliseconds: 500), vsync: this);
    _animation = new Tween(begin: 0.0, end: 1.0).animate(new CurvedAnimation(
      parent: _controller,
      curve: new Interval(0.0, 0.5)
    ))
      ..addListener(() {
        if (mounted){
          setState(() {
            // Refresh
          });
        }
      })
      ..addStatusListener((AnimationStatus status){
        if (status == AnimationStatus.completed){
          _controller.reverse().orCancel;
        } else if (status == AnimationStatus.dismissed){
          _controller.forward().orCancel;
        }
      });
    _controller.forward().orCancel;
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return new Positioned(
        top:  widget.position.dy,
        left: widget.position.dx,
        child: new IgnorePointer(
          child: new Material(
            color: Colors.transparent,
            child: new Opacity(
              opacity: _animation.value,
              child: widget.widget,
            ),
          ),
        ));
  }
}

要调用此BlinkingToast:

BlinkingToast toast = new BlinkingToast();

toast.show(
    context: context,
    externalBuilder: (BuildContext context){
        return new Icon(Icons.warning, color: Colors.purple);
    },
    duration: new Duration(seconds: 5),
    position: new Offset(50.0, 50.0),
);

结论

这是一篇非常简短的文章,仅旨在分享在任何屏幕上显示Widget的方式。

你可能感兴趣的:(Flutter 如何创建Toast或Notifications?叠加的概念)