logo
当前页

创建超级白板


说明
  • 本文档适用于开发以下平台的应用:iOS、Android。
  • 超级白板 SDK 不支持在 Android 8.0 设备上加载动态 PPT 文件。

概念解释

  • 超级白板 SDK、ZegoSuperBoard SDK:均指提供 ZEGO 超级白板服务(ZegoSuperBoard)的 SDK。
  • ZegoExpress-Video SDK:ZEGO 音视频互动 SDK,能够提供超级白板所需的实时信令传输的能力。超级白板 SDK 必须搭配此 SDK 使用。
  • ZegoSuperBoardView:在代码实现过程中,开发者用于展示的白板视图。
  • ZegoSuperBoardSubView:ZegoSuperBoardView 的子集,开发者实际创建的 View。ZegoSuperBoardView 会自动呈现最新创建或通过 switchSuperBoardSubView 指定的 ZegoSuperBoardSubView。
  • 纯白板:指定宽、高和页数创建的白板,用户在指定的白板画布上进行实时绘制。
  • 文件白板:基于文件创建的白板,白板宽高和页数由文件决定,实现在文件上绘制图元的业务需求。

前提条件

准备环境

在开始集成 ZegoSuperBoard SDK 前,请确保开发环境满足以下要求:

  • Flutter 版本需介乎 1.10.0 与 3.13.7 之间(包括这两个版本)。
  • 根据项目的运行设备,相关技术要求如下:

请配置开发环境如下:

  • Android Studio:“Preferences > Plugins”,搜索 “Flutter” 插件进行下载,并在插件中配置已经下载好的 Flutter 的 SDK 路径。
  • Visual Studio Code: 在应用商店中搜索 “Flutter” 扩展并下载。 以上任一开发环境配置好 Flutter 环境后,在终端执行 flutter doctor,根据提示内容补全相关未下载的依赖项。

(可选)新建项目

集成 SDK

  1. 在 Flutter 项目中,打开“pubspec.yaml” 文件,以“pub”形式添加“zego_superboard”和“zego_express_engine”依赖。

    Untitled
    ...
    dependencies:
        zego_superboard: 2.5.0+7 # 此处版本号仅为示例,如需了解其他可集成版本,请查看 zego_superboard 的 version 信息(https://pub.dev/packages/zego_superboard/versions)
        zego_express_engine: 3.10.3-whiteboard
    ...
    
    1
    Copied!
  2. 添加完成并保存文件后,在终端执行 flutter pub get

  3. 引入头文件

    在您的项目中,引入 zego_superboard SDK 和 zego_express_engine SDK 头文件。

    Untitled
    import 'package:zego_superboard/zego_superboard.dart';
    import 'package:zego_express_engine/zego_express_engine.dart';
    
    1
    Copied!
  4. 配置项目

实现超级白板

最简代码

ZEGO 提供了一个创建超级白板的最简示例代码,可作为开发中的参考。

运行本最简代码,您需要:

  1. 创建一个“zego_superboard_page.dart”文件,并将以下代码拷贝到对应文件中。

    Untitled
    import 'package:flutter/foundation.dart';
    import 'package:flutter/material.dart';
    import 'package:zego_superboard/zego_superboard.dart';
    import 'package:zego_superboard/zego_superboard_defines.dart';
    import 'package:zego_express_engine/zego_express_engine.dart';
    import 'package:zego_superboard/zego_superboard_event_handler.dart';
    
    class ZegoSuperboardPage extends StatefulWidget {
      const ZegoSuperboardPage({super.key});
    
      @override
      State<ZegoSuperboardPage> createState() => _ZegoSuperboardPageState();
    }
    
    class _ZegoSuperboardPageState extends State<ZegoSuperboardPage> {
      Widget? _previewViewWidget;
      late int currentViewID = -1;
      String userID = DateTime.now().millisecondsSinceEpoch.toString();
      String userName = "zego";
      String roomID = "567";
      int kAppID = ;  // 您的 AppID
      String kAppSign = "";  // 您的 AppSign
    
      ZegoSuperBoardManagerCreateResult? result;
      @override
      void initState() {
        super.initState();
        _initExpressSDK();
        _registerEventHandler();
      }
    
      /// 初始化RTC
      void _initExpressSDK() async {
        ZegoEngineProfile profile = ZegoEngineProfile(kAppID, ZegoScenario.Default,
            enablePlatformView: false, appSign: kAppSign);
        await ZegoExpressEngine.createEngineWithProfile(profile);
        _initSuperBoardSDK();
      }
    
      /// 初始化超级白板
      void _initSuperBoardSDK() async {
        ZegoSuperBoardInitConfig boardInitConfig =
            ZegoSuperBoardInitConfig(kAppID, kAppSign, userID: userID);
        //开启文件alpha环境
        ZegoSuperBoardManager.instance
            .setCustomizedConfig(key: 'set_alpha_env', value: 'true');
        ZegoSuperBoardError boardError =
            await ZegoSuperBoardManager.instance.initWithConfig(boardInitConfig);
        _createCanvasView();
        if (boardError.errorCode == 0) {
          print('超级白板初始化成功');
        } else {
          print('超级白板初始化失败');
        }
      }
    
      void _createCanvasView() async {
        await ZegoSuperBoardManager.instance.createCanvasView((viewID) {
          currentViewID = viewID;
        }).then((widget) {
          setState(() {
            _previewViewWidget = widget;
          });
        });
      }
    
      /// 注册监听
      void _registerEventHandler() {
        ZegoSuperBoardEventHandler.onError = ((errorCodde) {});
    
        ZegoSuperBoardEventHandler.onRemoteSuperBoardSubViewAdded =
            ((subViewModel) {
          if (kDebugMode) {
            print('[Flutter][onRemoteSuperBoardSubViewAdded]:$subViewModel');
          }
        });
    
        ZegoSuperBoardEventHandler.onRemoteSuperBoardSubViewRemoved =
            ((subViewModel) async {
          if (kDebugMode) {
            print('[Flutter][onRemoteSuperBoardSubViewRemoved]:$subViewModel');
          }
        });
    
        ZegoSuperBoardEventHandler.onRemoteSuperBoardSubViewSwitched = ((uniqueID) {
          if (kDebugMode) {
            print('[Flutter][onRemoteSuperBoardSubViewSwitched]:$uniqueID');
          }
        });
    
        ZegoSuperBoardEventHandler.onRemoteSuperBoardAuthChanged = ((authInfo) {
          if (kDebugMode) {
            print('[Flutter][onRemoteSuperBoardAuthChanged]:$authInfo');
          }
        });
    
        ZegoSuperBoardEventHandler.onRemoteSuperBoardGraphicAuthChanged =
            ((authInfo) {
          if (kDebugMode) {
            print('[Flutter][onRemoteSuperBoardGraphicAuthChanged]:$authInfo');
          }
        });
    
        ZegoSuperBoardEventHandler.onSuperBoardSubViewScrollChanged =
            ((uniqueID, page, pageCount) {
          if (kDebugMode) {
            print(
                '[Flutter][onRemoteSuperBoardGraphicAuthChanged] uniqueID:$uniqueID, uniqueID:$page');
          }
        });
        ZegoSuperBoardEventHandler.onSuperBoardSubViewSizeChanged =
            ((uniqueID, size) {
          if (kDebugMode) {
            print(
                '[Flutter][onSuperBoardSubViewSizeChanged] uniqueID:$uniqueID, size:$size');
          }
        });
    
        ZegoSuperBoardEventHandler.onStepChange = (() async {
          if (kDebugMode) {
            print('[Flutter][onStepChange]');
          }
        });
    
        ZegoSuperBoardEventHandler.uploadFile = ((info) async {
          if (kDebugMode) {
            print('[Flutter][uploadFile] info:$info');
          }
          if (info['state'] == ZegoSuperBoardUploadFileState.upload.value) {
            if (kDebugMode) {
              print(
                  '[Flutter][uploadFile] upload_percent:${info['upload_percent']}');
            }
          } else {
            Map? infoMap = info['infoMap'] as Map;
          }
        });
    
        ZegoSuperBoardEventHandler.uploadH5File = ((info) async {
          if (kDebugMode) {
            print('[Flutter][uploadH5File] info:$info');
          }
          if (info['state'] == ZegoSuperBoardUploadFileState.upload.value) {
            if (kDebugMode) {
              print(
                  '[Flutter][uploadH5File] upload_percent:${info['upload_percent']}');
            }
          } else {
            Map? infoMap = info['infoMap'] as Map;
          }
        });
    
        ZegoSuperBoardEventHandler.cacheFile = ((info) {
          if (kDebugMode) {
            print('[Flutter][cacheFile] info:$info');
          }
          if (info['state'] == ZegoSuperBoardCacheFileState.caching.value) {}
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return SafeArea(
            child: Container(
                color: Colors.white,
                child: Column(
                  children: [
                    Container(
                      color: Colors.grey.withOpacity(0.1),
                      width: double.infinity,
                      height: 248,
                      child: _previewViewWidget ?? Container(),
                    ),
                    const SizedBox(height: 10),
                    Expanded(
                      child: Column(
                        children: [
                          TextButton(
                              onPressed: _loginRoom,
                              child: const Text(
                                '登录房间',
                                style: TextStyle(
                                    fontSize: 18,
                                    color: Colors.black,
                                    fontWeight: FontWeight.bold),
                              )),
                          TextButton(
                              onPressed: _createWhiteboardView,
                              child: const Text(
                                '创建纯白板',
                                style: TextStyle(
                                    fontSize: 18,
                                    color: Colors.black,
                                    fontWeight: FontWeight.bold),
                              )),
                          TextButton(
                              onPressed: _createFileView,
                              child: const Text(
                                '创建文件白板',
                                style: TextStyle(
                                    fontSize: 18,
                                    color: Colors.black,
                                    fontWeight: FontWeight.bold),
                              )),
                          TextButton(
                            onPressed: _destroySuperBoardSubView,
                            child: const Text(
                              '销毁当前超级白板',
                              style: TextStyle(
                                  fontSize: 18,
                                  color: Colors.black,
                                  fontWeight: FontWeight.bold),
                            ),
                          ),
                        ],
                      ),
                    )
                  ],
                )));
      }
    
      /// 登录房间
      void _loginRoom() async {
        ZegoUser user = ZegoUser(userID, userName);
        ZegoRoomConfig config = ZegoRoomConfig.defaultConfig();
        config.isUserStatusNotify = true;
        ZegoRoomLoginResult result = await ZegoExpressEngine.instance
            .loginRoom(roomID, user, config: config);
        if (kDebugMode) {
          print(
              '[Flutter][loginRoom] result: $result  errorCode: ${result.errorCode}');
        }
      }
    
      /// 创建纯白板
      void _createWhiteboardView() async {
        ZegoCreateWhiteboardConfig whiteboardConfig = ZegoCreateWhiteboardConfig(
            '创建纯白板',
            perPageWidth: 16,
            perPageHeight: 9,
            pageCount: 5);
    
        result = await ZegoSuperBoardManager.instance
            .createWhiteboardView(config: whiteboardConfig);
        if (kDebugMode) {
          print(
              '[Flutter][createWhiteboardView] result: $result  errorCode: ${result!.boardError.errorCode}');
        }
      }
    
      /// 创建文件白板
      void _createFileView() async {
        // 创建文件白板前,需要通过 [uploadFile] 获取 fileID,此处为了演示,提供了一个可用的 fileID
        ZegoCreateFileConfig fileConfig = ZegoCreateFileConfig('Cz7ArXgd8lVTtqTp');
        ZegoSuperBoardManagerCreateResult result =
            await ZegoSuperBoardManager.instance.createFileView(fileConfig);
        if (kDebugMode) {
          print(
              '[Flutter][createFileView] result: $result  errorCode: ${result.boardError.errorCode}');
        }
      }
    
      /// 销毁白板
      void _destroySuperBoardSubView() async {
        if (result != null) {
          ZegoSuperBoardManager.instance
              .destroySuperBoardSubView(result!.model.uniqueID);
        }
      }
    }
    
    1
    Copied!
  2. 在“main.dart”文件引入创建好的路由ZegoSuperboardPage

    Untitled
    ...
    Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('Superboard view flutter app'),
            ),
          body: const ZegoSuperboardPage(),
          ),
        );
      }
    ...
    
    1
    Copied!

1 初始化 SDK

初始化 ZEGO Express SDK

调用 ZEGO Express SDK 的 createEngineWithProfile 接口,将申请到的 AppID 和 AppSign 传入参数 “appID” 和 “appSign”,创建引擎单例对象。

Untitled
int appID = ;  // 您的 appid
String appSign = '';  // 您的 appSign
ZegoScenario scenario = ZegoScenario.Default;
ZegoEngineProfile profile = ZegoEngineProfile(appID, scenario, enablePlatformView: false, appSign: appSign);
await ZegoExpressEngine.createEngineWithProfile(profile);
1
Copied!

初始化 ZegoSuperBoard SDK

调用initWithConfig 方法初始化 ZegoSuperBoard SDK。

如果返回值 为 0,代表初始化成功,可进行更多操作。errorCode 可参考 常见错误码

Untitled
/**
* appID:ZEGO 为开发者签发的应用 ID,请从 ZEGO 控制台 https://console.zego.im 申请。
* appSign: ZEGO 为开发者签发的应用 appSign,请从 ZEGO 控制台 https://console. zego.im 申请
*/
//创建一个初始化配置类
String userID = 'userid';
ZegoSuperBoardInitConfig boardInitConfig = ZegoSuperBoardInitConfig(appID, appSign, userID: userID);
ZegoSuperBoardError boardError = await ZegoSuperBoardManager.instance.initWithConfig(boardInitConfig);
1
Copied!
说明

需要确保 ZEGO Express SDK 和 ZegoSuperBoard SDK 均初始化成功,才能成功调用其他接口。

2 监听事件回调

根据实际应用需要,在初始化 SuperBoard 后监听想要关注的事件回调,比如错误提醒、远端新增白板文件、远端删除白板文件、远端切换白板文件等。 SuperBoard 自动实现了多端同步能力,远端通知回调内只需刷新本地UI逻辑即可。

Untitled
ZegoSuperBoardEventHandler.onError = ((errorCodde) {});
ZegoSuperBoardEventHandler.onRemoteSuperBoardSubViewAdded = ((subViewModel) {
    if (kDebugMode) {
        print('[Flutter][onRemoteSuperBoardSubViewAdded]:$subViewModel');
    }
});
ZegoSuperBoardEventHandler.onRemoteSuperBoardSubViewRemoved = ((subViewModel) {
    if (kDebugMode) {
        print('[Flutter][onRemoteSuperBoardSubViewRemoved]:$subViewModel');
    }
});
ZegoSuperBoardEventHandler.onRemoteSuperBoardSubViewSwitched = ((uniqueID) {
    if (kDebugMode) {
        print('[Flutter][onRemoteSuperBoardSubViewSwitched]:$uniqueID');
    }
});
ZegoSuperBoardEventHandler.onRemoteSuperBoardAuthChanged = ((authInfo) {
    if (kDebugMode) {
        print('[Flutter][onRemoteSuperBoardAuthChanged]:$authInfo');
    }
});
ZegoSuperBoardEventHandler.onRemoteSuperBoardGraphicAuthChanged = ((authInfo) {
    if (kDebugMode) {
        print('[Flutter][onRemoteSuperBoardGraphicAuthChanged]:$authInfo');
    }
});
ZegoSuperBoardEventHandler.onSuperBoardSubViewScrollChanged = ((uniqueID, page, pageCount) {
    if (kDebugMode) {
        print(
            '[Flutter][onRemoteSuperBoardGraphicAuthChanged] uniqueID:$uniqueID, uniqueID:$page');
    }
});
ZegoSuperBoardEventHandler.onSuperBoardSubViewSizeChanged = ((uniqueID, size) {
    if (kDebugMode) {
        print(
            '[Flutter][onSuperBoardSubViewSizeChanged] uniqueID:$uniqueID, size:$size');
    }
});
1
Copied!

3 登录房间

调用 ZegoExpressEngineloginRoom 接口登录房间。

  • 需保证 “roomID” 信息的全局唯一。
  • “userID” 与 “userName” 不能为 “nil” 否则会导致登录房间失败。
  • ZegoUser 的构造方法 ZegoUser 会将 userName 设为与传的参数 userID 一样。
  • 每个 “userID” 必须唯一,建议设置成一个有意义的值,开发者可将 “userID” 与自己业务账号系统进行关联。 错误码详情请参考 登录房间错误码
Untitled
void loginRoom() {
// roomID 由您本地生成,需保证 “roomID” 全局唯一。不同用户要登陆同一个房间才能进行通话
String roomID = "room1";
// 创建用户对象,ZegoUser 的构造方法 userWithUserID 会将 “userName” 设为与传的参数 “userID” 一样。“userID” 与 “userName” 不能为 “nil”,否则会导致登录房间失败。
// userID 由您本地生成,需保证 “userID” 全局唯一。
ZegoUser user = ZegoUser('user1', 'userName');
// 只有传入 “isUserStatusNotify” 参数取值为 “true” 的 ZegoRoomConfig,才能收到 onRoomUserUpdate 回调。
ZegoRoomConfig config = ZegoRoomConfig.defaultConfig();
config.isUserStatusNotify = true;
// 登录房间
ZegoExpressEngine.instance
.loginRoom(roomID, user, config: config)
.then((result) => {});
}
1
Copied!

4 添加白板视图

确保 ZegoSuperBoard SDK 初始化成功且登录房间之后,才能调用 createCanvasView 接口,获取业务场景 Widget,用来将白板视图(ZegoSuperBoardView)直接添加到您的业务场景视图中。示例代码如下:

Untitled
// 将此 Widget 加入到页面的渲染树中以显示白板画面
//
_containerWidget = await ZegoSuperBoardManager.instance.createCanvasView((viewID){

});
1
Copied!

5 创建白板

创建白板前需要保证登录成功,建议可在登录成功的回调中调用创建纯白板或文件白板的接口。

Untitled
// 创建白板前需要保证登录成功,即房间回调状态为 ZegoRoomState.Connected
ZegoExpressEngine.onRoomStateUpdate = (String roomID, ZegoRoomState state,
int errorCode, Map<String, dynamic> extendedData) {
    if (state == ZegoRoomState.Connected && errorCode == 0) {
        // 表示登录成功,需要在登录成功后才可以创建白板
    }
};
1
Copied!

超级白板支持创建纯白板和文件白板。

  • 纯白板:指定宽、高和页数创建的白板,用户在指定的白板画布上进行实时绘制。
  • 文件白板:基于文件创建的白板,白板宽高和页数由文件决定,实现在文件上绘制图元的业务需求。 创建文件白板前需要先获取文件的 fileID,可参考 共享文件管理 进行上传。

一个房间内最多可创建 50 个白板,房间内已存在 50 个白板时再创建白板会失败。 如需获取房间内当前的白板数量,请调用 querySuperBoardSubViewList

创建纯白板创建文件白板
Untitled
// 创建白板需要构造
ZegoCreateWhiteboardConfig whiteboardConfig = ZegoCreateWhiteboardConfig(
    '一个测试白板',
    perPageWidth: 16,
    perPageHeight: 9,
    pageCount: ZogoManager.instance.pageCount);
ZegoSuperBoardManagerCreateResult? result = await ZegoSuperBoardManager.instance.createWhiteboardView(config: whiteboardConfig);
1
Copied!
Untitled
// 创建文件需要构造 ZegoCreateFileConfig 配置类
ZegoCreateFileConfig fileConfig = ZegoCreateFileConfig(currentfileID);
ZegoSuperBoardManagerCreateResult result = await ZegoSuperBoardManager.instance.createFileView(fileConfig);
if (kDebugMode) {
   print('[Flutter][createFileView] result:$result');
}
1
Copied!

6 销毁白板

如需销毁某个白板,调用 destroySuperBoardSubView 接口,传入该白板的 uniqueID,即可销毁。

Untitled
//销毁后SDK内部会自动切换到另外一个白板。展示的白板为销毁白板的上一个
await ZegoSuperBoardManager.instance.destroySuperBoardSubView('uniqueID');
1
Copied!

测试你的 App

使用多台设备运行上述项目,登录同一房间 ID。用手指在任一设备的 ZegoSuperBoardView 的范围内按下移动,即可看到涂鸦效果展示在各个设备 ZegoSuperBoardView 上。

了解更多

到此为止,您已成功构建一个简单的超级白板应用。接下来,您可通过以下文档,进一步体验超级白板功能:

Previous

跑通示例源码

Next

白板绘制