🚀

【Flutter】独自SnackBarで位置を上!margin使わないから画面タップに影響なし

2023/11/29に公開

通常のSnackBarでは、SnackBarをmarginで上部に表示中は画面タップが効かなくなります。

void showSnackBar({
  required BuildContext context,
  required String message,
}) {
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text(
        'SnackBarを表示します',
        style: ...省略
      ),
      duration: const Duration(seconds: 1),
      backgroundColor: const Color(0xFF333333).withOpacity(0.8),
      behavior: SnackBarBehavior.floating,
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
      margin: EdgeInsets.only(
        bottom: MediaQuery.of(context).size.height - 150, // ここで無理やり上にしている
        left: 16,
        right: 16,
      ),
    ),
  );
}

そこで、Overlayを使って、SnackBarを再現してPositionで浮かせることで解決できます。

void showSnackBar({
  required BuildContext context,
  required String message,
  Duration duration = const Duration(seconds: 1),
}) {
  final overlay = Overlay.of(context);
  final overlayEntry = OverlayEntry(
    builder: (context) => Positioned(
      top: 70,
      left: 16,
      right: 16,
      child: Material(
        color: Colors.transparent,
        child: Container(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
          decoration: BoxDecoration(
            color: const Color(0xFF333333).withOpacity(0.8),
            borderRadius: BorderRadius.circular(5),
            boxShadow: const [
              BoxShadow(
                color: Colors.black26,
                offset: Offset(0, 5),
                blurRadius: 5,
                spreadRadius: 1,
              ),
            ],
          ),
          child: Text(
            message,
            style: ...省略,
          ),
        ),
      ),
    ),
  );

  overlay.insert(overlayEntry);

  Future.delayed(duration, () => overlayEntry.remove());
}

ちなみにDismissibleを使うことで、SnackBar表示後に上にスワイプして消すことも再現できます。

void showSnackBar({
  required BuildContext context,
  required String message,
  Duration duration = const Duration(seconds: 100),
}) {
  final overlay = Overlay.of(context);

  late OverlayEntry overlayEntry;
  overlayEntry = OverlayEntry(
    builder: (context) => Positioned(
      top: 70,
      left: 16,
      right: 16,
      child: Material(
        color: Colors.transparent,
        child: Dismissible(
          direction: DismissDirection.up,
          onDismissed: (direction) => overlayEntry.remove(),
          key: ValueKey(message),
          child: Container(
            padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 13),
            decoration: BoxDecoration(
              color: const Color(0xFF333333).withOpacity(0.8),
              borderRadius: BorderRadius.circular(5),
              boxShadow: const [
                BoxShadow(
                  color: Colors.black26,
                  offset: Offset(0, 5),
                  blurRadius: 5,
                  spreadRadius: 1,
                ),
              ],
            ),
            child: Text(
              message,
              style: ...省略,
            ),
          ),
        ),
      ),
    ),
  );

  overlay.insert(overlayEntry);

  Future.delayed(duration, () => overlayEntry.remove());
}

Discussion