鸿蒙跨设备协同绘画板开发指南 原创

进修的泡芙
发布于 2025-6-18 22:13
浏览
0收藏

鸿蒙跨设备协同绘画板开发指南

一、项目概述

本文将基于HarmonyOS开发一个多设备实时同步的协同绘画板应用,借鉴《鸿蒙跨端U同步》中游戏多设备同步的技术原理,实现多个设备间实时同步绘制内容的功能。该系统将包含画板绘制、颜色选择、笔触调整以及实时同步等核心功能。

二、技术架构

±--------------------+ ±--------------------+
绘画界面 <-----> 绘制同步服务
(DrawingSlice) (DrawingSyncService)
±---------±---------+ ±---------±---------+

±---------v----------+ ±---------v----------+
画布绘制引擎 分布式数据管理
(CanvasRenderer) (DistributedData)
±---------±---------+ ±---------±---------+

±---------v-----------------------------v----------+
HarmonyOS基础服务 |

±--------------------------------------------------+

三、核心代码实现
绘制数据模型

public class DrawingAction {
public enum ActionType { START, MOVE, END }

private ActionType type;
private float x;
private float y;
private int color;
private float strokeWidth;
private String deviceId;

// 构造方法
public DrawingAction(ActionType type, float x, float y, int color, float strokeWidth) {
    this.type = type;
    this.x = x;
    this.y = y;
    this.color = color;
    this.strokeWidth = strokeWidth;
    this.deviceId = DistributedDeviceInfo.getLocalDeviceId();

// JSON序列化

public JSONObject toJson() throws JSONException {
    JSONObject json = new JSONObject();
    json.put("type", type.name());
    json.put("x", x);
    json.put("y", y);
    json.put("color", color);
    json.put("strokeWidth", strokeWidth);
    json.put("deviceId", deviceId);
    json.put("timestamp", System.currentTimeMillis());
    return json;

// JSON反序列化

public static DrawingAction fromJson(JSONObject json) throws JSONException {
    DrawingAction action = new DrawingAction(
        ActionType.valueOf(json.getString("type")),
        (float) json.getDouble("x"),
        (float) json.getDouble("y"),
        json.getInt("color"),
        (float) json.getDouble("strokeWidth")
    );
    action.deviceId = json.getString("deviceId");
    return action;

// getter方法…

绘制同步服务

public class DrawingSyncService {
private static final String TAG = “DrawingSyncService”;
private static final String SYNC_CHANNEL = “drawing_sync”;
private static DrawingSyncService instance;

private DistributedDataManager dataManager;
private DrawingActionListener actionListener;

private DrawingSyncService(Context context) {
    this.dataManager = DistributedDataManagerFactory.getInstance()
        .createDistributedDataManager(context);
    initDataListener();

public static synchronized DrawingSyncService getInstance(Context context) {

    if (instance == null) {
        instance = new DrawingSyncService(context);

return instance;

private void initDataListener() {

    dataManager.registerDataChangeListener(SYNC_CHANNEL, new DataChangeListener() {
        @Override
        public void onDataChanged(String deviceId, String key, String value) {
            try {
                JSONObject json = new JSONObject(value);
                DrawingAction action = DrawingAction.fromJson(json);
                
                // 忽略本地设备发送的更新
                if (!deviceId.equals(DistributedDeviceInfo.getLocalDeviceId())) {
                    if (actionListener != null) {
                        actionListener.onActionReceived(action);

}

catch (JSONException e) {

                HiLog.error(TAG, "Failed to parse drawing action");

}

    });

// 同步绘制动作

public void syncDrawingAction(DrawingAction action) {
    try {
        JSONObject json = action.toJson();
        
        DistributedOptions options = new DistributedOptions();
        options.setPriority(DistributedOptions.Priority.HIGH);
        
        dataManager.putString(SYNC_CHANNEL, 
            json.toString(), 
            DistributedDataManager.PUT_MODE_RELIABLE, 
            options);

catch (JSONException e) {

        HiLog.error(TAG, "Failed to serialize drawing action");

}

// 注册动作监听器
public void registerActionListener(DrawingActionListener listener) {
    this.actionListener = listener;

// 取消注册监听器

public void unregisterActionListener() {
    this.actionListener = null;

public interface DrawingActionListener {

    void onActionReceived(DrawingAction action);

}

画布绘制组件

public class DrawingView extends Component implements Component.DrawTask, Component.TouchEventListener {
private static final String TAG = “DrawingView”;

private Paint paint;
private Path currentPath;
private List<DrawingPath> paths = new ArrayList<>();
private DrawingSyncService syncService;

// 当前绘制属性
private int currentColor = Color.BLACK;
private float currentStrokeWidth = 5f;

public DrawingView(Context context) {
    super(context);
    init();

public DrawingView(Context context, AttrSet attrSet) {

    super(context, attrSet);
    init();

private void init() {

    addDrawTask(this);
    setTouchEventListener(this);
    
    paint = new Paint();
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeCap(Paint.StrokeCap.ROUND);
    paint.setStrokeJoin(Paint.StrokeJoin.ROUND);
    
    syncService = DrawingSyncService.getInstance(getContext());

@Override

public void onDraw(Component component, Canvas canvas) {
    // 绘制所有路径
    for (DrawingPath path : paths) {
        paint.setColor(path.getColor());
        paint.setStrokeWidth(path.getStrokeWidth());
        canvas.drawPath(path.getPath(), paint);

// 绘制当前路径

    if (currentPath != null) {
        paint.setColor(currentColor);
        paint.setStrokeWidth(currentStrokeWidth);
        canvas.drawPath(currentPath, paint);

}

@Override
public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
    float x = touchEvent.getPointerPosition(0).getX();
    float y = touchEvent.getPointerPosition(0).getY();
    
    switch (touchEvent.getAction()) {
        case TouchEvent.PRIMARY_POINT_DOWN:
            startNewPath(x, y);
            syncService.syncDrawingAction(
                new DrawingAction(DrawingAction.ActionType.START, x, y, currentColor, currentStrokeWidth));
            break;
            
        case TouchEvent.POINT_MOVE:
            if (currentPath != null) {
                currentPath.lineTo(x, y);
                syncService.syncDrawingAction(
                    new DrawingAction(DrawingAction.ActionType.MOVE, x, y, currentColor, currentStrokeWidth));
                invalidate();

break;

        case TouchEvent.POINT_UP:
            if (currentPath != null) {
                paths.add(new DrawingPath(currentPath, currentColor, currentStrokeWidth));
                currentPath = null;
                syncService.syncDrawingAction(
                    new DrawingAction(DrawingAction.ActionType.END, x, y, currentColor, currentStrokeWidth));
                invalidate();

break;

return true;

private void startNewPath(float x, float y) {

    currentPath = new Path();
    currentPath.moveTo(x, y);

// 设置画笔颜色

public void setColor(int color) {
    this.currentColor = color;

// 设置画笔粗细

public void setStrokeWidth(float width) {
    this.currentStrokeWidth = width;

// 清除画布

public void clear() {
    paths.clear();
    currentPath = null;
    invalidate();

private static class DrawingPath {

    private Path path;
    private int color;
    private float strokeWidth;
    
    public DrawingPath(Path path, int color, float strokeWidth) {
        this.path = path;
        this.color = color;
        this.strokeWidth = strokeWidth;

// getter方法…

}

绘画界面实现

public class DrawingSlice extends AbilitySlice {
private static final String TAG = “DrawingSlice”;

private DrawingView drawingView;
private DrawingSyncService syncService;

@Override
public void onStart(Intent intent) {
    super.onStart(intent);
    setUIContent(ResourceTable.Layout_drawing_layout);
    
    // 初始化服务
    syncService = DrawingSyncService.getInstance(this);
    
    // 获取UI组件
    drawingView = (DrawingView) findComponentById(ResourceTable.Id_drawing_view);
    Button clearBtn = (Button) findComponentById(ResourceTable.Id_clear_btn);
    Button colorRed = (Button) findComponentById(ResourceTable.Id_color_red);
    Button colorGreen = (Button) findComponentById(ResourceTable.Id_color_green);
    Button colorBlue = (Button) findComponentById(ResourceTable.Id_color_blue);
    Slider strokeSlider = (Slider) findComponentById(ResourceTable.Id_stroke_slider);
    
    // 设置按钮事件
    clearBtn.setClickedListener(component -> drawingView.clear());
    
    colorRed.setClickedListener(component -> drawingView.setColor(Color.RED));
    colorGreen.setClickedListener(component -> drawingView.setColor(Color.GREEN));
    colorBlue.setClickedListener(component -> drawingView.setColor(Color.BLUE));
    
    strokeSlider.setValueChangedListener((slider, value, fromUser) -> {
        if (fromUser) {
            drawingView.setStrokeWidth(value);

});

    // 注册同步监听器
    syncService.registerActionListener(new DrawingSyncService.DrawingActionListener() {
        @Override
        public void onActionReceived(DrawingAction action) {
            getUITaskDispatcher().asyncDispatch(() -> {
                handleRemoteAction(action);
            });

});

private void handleRemoteAction(DrawingAction action) {

    float x = action.getX();
    float y = action.getY();
    
    switch (action.getType()) {
        case START:
            drawingView.startNewPath(x, y);
            break;
            
        case MOVE:
            if (drawingView.getCurrentPath() != null) {
                drawingView.getCurrentPath().lineTo(x, y);

break;

        case END:
            if (drawingView.getCurrentPath() != null) {
                drawingView.getPaths().add(
                    new DrawingView.DrawingPath(drawingView.getCurrentPath(), 
                        action.getColor(), action.getStrokeWidth()));
                drawingView.setCurrentPath(null);

break;

drawingView.invalidate();

@Override

protected void onStop() {
    super.onStop();
    // 取消注册监听器
    syncService.unregisterActionListener();

}

XML布局文件

<DirectionalLayout
xmlns:ohos=“http://schemas.huawei.com/res/ohos
ohos:width=“match_parent”
ohos:height=“match_parent”
ohos:orientation=“vertical”>

<com.example.drawing.DrawingView
    ohos:id="$+id:drawing_view"
    ohos:width="match_parent"
    ohos:height="0vp"
    ohos:weight="1"
    ohos:background_element="#FFFFFFFF"/>
    
<DirectionalLayout
    ohos:width="match_parent"
    ohos:height="wrap_content"
    ohos:orientation="horizontal"
    ohos:padding="8vp"
    ohos:margin_top="8vp">
    
    <Button
        ohos:id="$+id:color_red"
        ohos:width="40vp"
        ohos:height="40vp"
        ohos:background_element="#FFFF0000"
        ohos:margin_right="8vp"/>
        
    <Button
        ohos:id="$+id:color_green"
        ohos:width="40vp"
        ohos:height="40vp"
        ohos:background_element="#FF00FF00"
        ohos:margin_right="8vp"/>
        
    <Button
        ohos:id="$+id:color_blue"
        ohos:width="40vp"
        ohos:height="40vp"
        ohos:background_element="#FF0000FF"
        ohos:margin_right="16vp"/>
        
    <Slider
        ohos:id="$+id:stroke_slider"
        ohos:width="200vp"
        ohos:height="wrap_content"
        ohos:min_value="1"
        ohos:max_value="20"
        ohos:progress_value="5"/>
        
    <Button
        ohos:id="$+id:clear_btn"
        ohos:width="80vp"
        ohos:height="40vp"
        ohos:text="清除"
        ohos:margin_left="16vp"/>
</DirectionalLayout>

</DirectionalLayout>

四、与游戏同步技术的结合点
实时动作同步:类似游戏中玩家操作的实时同步,实现绘制动作的即时传输

数据压缩优化:借鉴游戏网络优化技术,减少绘制数据传输量

冲突解决机制:采用时间戳解决多设备同时绘制时的冲突

状态一致性:确保所有设备的画布状态最终一致

设备管理:利用HarmonyOS分布式能力实现设备自动发现和连接

五、项目扩展方向
更多绘制工具:添加矩形、圆形等形状绘制工具

图层管理:支持多层绘制和图层控制

历史记录:实现撤销/重做功能

图片导入:支持导入背景图片进行绘制

协作管理:创建和管理协作会话

六、总结

本协同绘画板应用实现了以下功能:
基于Canvas的流畅绘制体验

多设备间实时同步绘制内容

可自定义的画笔颜色和粗细

简洁直观的用户界面

高效的分布式数据传输

通过借鉴游戏中的同步技术,我们构建了一个高性能的跨设备协同绘画系统。该系统不仅适用于创意协作场景,也为开发者展示了如何利用HarmonyOS分布式能力构建复杂的实时协同应用。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
收藏
回复
举报
回复
    相关推荐