Flutter气泡动画插件awesome_bubble的使用

Flutter气泡动画插件awesome_bubble的使用

简介

awesome_bubble 是一个用于在所有其他应用程序之上显示消息气泡的 Flutter 插件。该插件可以显示覆盖层气泡和通知。

支持平台

Android

关于

awesome_bubble 提供了快速且简便地显示消息气泡的功能。只需按照以下步骤操作即可。

该插件完全依赖于在所有应用之上显示气泡,因此它仅适用于 Android 系统,因为 iOS 不提供此功能。

使用方法

  1. 首先,在 pubspec.yaml 文件中添加插件:

    awesome_bubble: ^updated_version
    
  2. 转到 AndroidManifest.xml 文件以添加应用程序所需的权限:

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    
  3. 现在你可以快速使用该插件,并可以查看 示例项目 以轻松发现所有优点。

插件功能

initService

必须在开始时使用,以便能够使用插件。它需要一组属性来设置气泡。

参数 必填 描述
screenHeight 是 需要从 MediaQuery 发送逻辑像素屏幕高度
chatHeadIcon 否 如果需要更改聊天头图标,可以在 drawable 文件夹中添加新图标,并传递图像名称(不带格式)。如果未传递图标,默认图标为插件内部的 android chat head icon。
notificationIcon 否 与 chatHeadIcon 相同
当未传递图标时,默认图标为插件内部的 android notification icon。
notificationTitle 否 当关闭聊天头时,会显示包含标题和正文的通知。如果不发送任何标题,默认通知标题为 App Name
notificationBody 否 与 notificationTitle 相同,但默认通知正文为 Your Service is still working
notificationCircleHexColor 否 在 Android 13 及以上版本,系统会将通知图标置于圆圈内。可以通过此参数更改圆圈颜色。
但在 Android 10 及以下版本,此参数用于更改通知图标

checkPermission

用于检查应用程序是否具有显示在所有应用之上的权限。此函数返回布尔值。

  • true 表示已允许权限,因此可以启动气泡。
  • false 表示未允许权限,因此需要首先使用 askPermission 函数。

askPermission

通过使用此函数,你将被自动引导至系统设置,具体是在与显示在其他应用之上的部分相关的区域,以便给你的应用授予此权限。

startService

通过此函数可以启动服务以显示气泡。

参数 必填 描述
notificationTitle 否 如果要在启动服务之前更改通知标题,可以通过传递新的通知标题来实现,否则如果在 initService 中使用了通知标题,则服务将使用它。

stopService

通过此函数可以停止服务以关闭气泡。

clearNotificationService

通过此函数可以清除状态栏中的气泡通知。

示例代码

以下是完整的示例代码,展示了如何使用 awesome_bubble 插件。

import 'dart:async';
import 'dart:developer';
import 'dart:math' as math;

import 'package:awesome_bubble/awesome_bubble.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

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

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  [@override](/user/override)
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  String msg = 'Unknown';
  final _bubblePlugin = BubbleService.I;
  final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
      FlutterLocalNotificationsPlugin();

  bool inTrip = false;

  bool serviceStarted = true;

  [@override](/user/override)
  void didChangeAppLifecycleState(AppLifecycleState state) {
    log(state.name, name: 'APP STATE');
    log(inTrip.toString(), name: 'IS SERVICE WORKING?');

    if (!inTrip) return;
    switch (state) {
      case AppLifecycleState.resumed:
        clearNotificationService();
        stopService();
        break;
      case AppLifecycleState.paused:
        startService();
        break;
      default:
        break;
    }
  }

  [@override](/user/override)
  void initState() {
    clearNotificationService();
    WidgetsBinding.instance.addObserver(this);
    // 请求 Android 13 及以上的通知权限
    flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()?
        .requestPermission();

    // 初始化服务,不初始化服务则无法启动
    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
      _bubblePlugin
          .initService(
        screenHeight: MediaQuery.sizeOf(context).height,
      )
          .then((value) {
        serviceStarted = value ?? false;
        setState(() {});
      });
    });
    super.initState();
  }

  [@override](/user/override)
  void didChangeMetrics() {
    stopService();
    super.didChangeMetrics();
  }

  [@override](/user/override)
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  Future<void> startService() async {
    bool serviceWorks;
    try {
      final double navigationBar = await getNavHeight();
      if (!mounted) return;
      final double density = MediaQuery.of(context).devicePixelRatio;
      final double screenHeight = MediaQuery.of(context).size.height;
      final double screenWidth = MediaQuery.of(context).size.width * density;
      log(
        {
          'screenWidth-P.P': screenWidth,
          'screenHeight-L.P': screenHeight,
          'density-DPR': density,
          'navigationBarHeight': navigationBar,
        }.toString(),
        name: 'From Flutter Side'.toUpperCase(),
      );
      int randomNum = math.Random().nextInt(500);
      serviceWorks = await _bubblePlugin.startService() ?? false;

      if (!serviceWorks) return;
    } on PlatformException catch (e) {
      serviceWorks = false;
      log(e.message.toString(), name: 'START SERVICE ERROR');
    }
    if (!mounted) return;

    setState(() {
      msg = (serviceWorks
          ? "Success starting chat head service"
          : "Error when starting chat head service");
    });
  }

  Future<void> stopService() async {
    bool serviceStop;
    try {
      serviceStop = await _bubblePlugin.stopService() ?? false;
    } on PlatformException catch (e) {
      log(e.message.toString(), name: 'START SERVICE ERROR');
      serviceStop = false;
    }
    if (!mounted) return;

    setState(() {
      msg = (serviceStop
          ? "Success stop chat head service"
          : "Error when stop hat head service");
    });
  }

  Future<void> clearNotificationService() async =>
      _bubblePlugin.clearNotificationService();

  Future<double> getNavHeight() async {
    final devicePixelRatio = MediaQuery.of(context).devicePixelRatio;
    final screenHeight = MediaQuery.of(context).size.height;

    final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin();
    final AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo;
    final deviceHeight = androidInfo.displayMetrics.heightPx;

    final androidNavHeight = deviceHeight / devicePixelRatio - screenHeight;
    return androidNavHeight;
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        useMaterial3: true,
      ),
      home: Scaffold(
        backgroundColor: Colors.white,
        appBar: AppBar(
          title: const Text('Bubble Plugin'),
        ),
        body: SizedBox(
          width: MediaQuery.of(context).size.width,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Text('$msg\n'),
              if (serviceStarted) ...[
                const SizedBox(height: 20),
                ElevatedButton(
                  onPressed: () async {
                    if (!inTrip) {
                      final hasAPermission =
                          await _bubblePlugin.checkPermission();

                      if (!(hasAPermission ?? true)) {
                        await _bubblePlugin.askPermission();
                        return;
                      }
                    }
                    inTrip = !inTrip;

                    log(inTrip.toString(), name: 'bubbleIsWorking');
                    setState(() {});
                  },
                  child: Text(inTrip ? 'Stop trip' : 'Start trip'),
                ),
              ]
            ],
          ),
        ),
      ),
    );
  }
}

class BubbleService {
  BubbleService._();
  static BubbleService I = BubbleService._();

  final AwesomeBubble _bubble = AwesomeBubble.I;

  Future<bool?> initService({
    required double screenHeight,
    String? chatHeadIcon,
    String? notificationIcon,
    String? notificationTitle,
    int? notificationCircleHexColor,
    String? notificationBody,
  }) =>
      _bubble.initService(
        screenHeight: screenHeight,
        chatHeadIcon: chatHeadIcon,
        notificationIcon: notificationIcon,
        notificationTitle: notificationTitle,
        notificationCircleHexColor: notificationCircleHexColor,
        notificationBody: notificationBody,
      );

  Future<bool?> startService({String? notificationTitle}) =>
      _bubble.startService(
        notificationTitle: notificationTitle,
      );

  Future<bool?> stopService() => _bubble.stopService();

  Future<bool?> checkPermission() => _bubble.checkPermission();

  Future<bool?> askPermission() => _bubble.askPermission();

  Future<bool?> clearNotificationService() =>
      _bubble.clearServiceNotification();
}

更多关于Flutter气泡动画插件awesome_bubble的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter气泡动画插件awesome_bubble的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


awesome_bubble 是一个用于在 Flutter 应用中创建气泡动画效果的插件。它可以帮助你轻松地在应用中添加动态的气泡效果,通常用于背景装饰或增强用户体验。

安装

首先,你需要在 pubspec.yaml 文件中添加 awesome_bubble 依赖:

dependencies:
  flutter:
    sdk: flutter
  awesome_bubble: ^1.0.0  # 确保使用最新的版本

然后运行 flutter pub get 来安装依赖。

基本用法

awesome_bubble 提供了 Bubble 小部件,你可以将它添加到你的 UI 中。以下是一个简单的示例,展示如何在应用中使用气泡动画:

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

class BubbleExample extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Bubble Animation Example'),
      ),
      body: Center(
        child: Container(
          width: 300,
          height: 300,
          child: Bubble(
            child: Center(
              child: Text(
                'Hello, Bubble!',
                style: TextStyle(fontSize: 24, color: Colors.white),
              ),
            ),
            color: Colors.blue,
            radius: 100,
            duration: Duration(seconds: 3),
            repeat: true,
          ),
        ),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    home: BubbleExample(),
  ));
}

参数说明

  • child: 气泡内部的子组件,通常是你想要显示的文本或图标。
  • color: 气泡的颜色。
  • radius: 气泡的半径。
  • duration: 气泡动画的持续时间。
  • repeat: 是否重复播放动画。

自定义气泡动画

你还可以通过 BubbleController 来控制气泡的动画。例如,你可以手动启动、停止或重置动画:

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

class CustomBubbleExample extends StatefulWidget {
  [@override](/user/override)
  _CustomBubbleExampleState createState() => _CustomBubbleExampleState();
}

class _CustomBubbleExampleState extends State<CustomBubbleExample> {
  BubbleController _bubbleController = BubbleController();

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Custom Bubble Animation'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              width: 200,
              height: 200,
              child: Bubble(
                controller: _bubbleController,
                child: Center(
                  child: Text(
                    'Custom Bubble',
                    style: TextStyle(fontSize: 24, color: Colors.white),
                  ),
                ),
                color: Colors.green,
                radius: 80,
                duration: Duration(seconds: 2),
                repeat: false,
              ),
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                _bubbleController.start();
              },
              child: Text('Start Animation'),
            ),
            ElevatedButton(
              onPressed: () {
                _bubbleController.stop();
              },
              child: Text('Stop Animation'),
            ),
            ElevatedButton(
              onPressed: () {
                _bubbleController.reset();
              },
              child: Text('Reset Animation'),
            ),
          ],
        ),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    home: CustomBubbleExample(),
  ));
}
回到顶部