You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android实时绘图应用:Firebase RTDB中Path数据的存储类型与键设计

How to Store Android Canvas Paths in Firebase Realtime Database

Great question! Since Firebase Realtime Database can't directly serialize Android's Path object (it's platform-specific and doesn't play nice with Firebase's supported data types), you'll need to convert your paths into a serializable format first, then structure your keys to fit your app's use case. Let's break this down step by step:

1. Convert Path to a Serializable Model

You have two main options here, depending on whether you need to support complex paths (like curves or bezier shapes) or just basic lines:

Option A: Full Path Operation Tracking (Supports All Path Types)

This method records every operation that builds the Path (moveTo, lineTo, quadTo, etc.), which lets you fully reconstruct the original path later. Create two simple model classes to hold this data:

// PathModel stores the full path data
public class PathModel {
    private List<PathOperation> operations;
    private int fillType;

    // Required empty constructor for Firebase deserialization
    public PathModel() {}

    public PathModel(Path path) {
        this.fillType = path.getFillType().ordinal();
        this.operations = new ArrayList<>();

        // Iterate through the path to extract all operations
        PathIterator iterator = path.getPathIterator(null);
        float[] coords = new float[6];

        while (!iterator.isDone()) {
            int type = iterator.currentSegment(coords);
            switch (type) {
                case PathIterator.SEG_MOVETO:
                    operations.add(new PathOperation("moveTo", coords[0], coords[1]));
                    break;
                case PathIterator.SEG_LINETO:
                    operations.add(new PathOperation("lineTo", coords[0], coords[1]));
                    break;
                case PathIterator.SEG_QUADTO:
                    operations.add(new PathOperation("quadTo", coords[0], coords[1], coords[2], coords[3]));
                    break;
                case PathIterator.SEG_CUBICTO:
                    operations.add(new PathOperation("cubicTo", coords[0], coords[1], coords[2], coords[3], coords[4], coords[5]));
                    break;
                case PathIterator.SEG_CLOSE:
                    operations.add(new PathOperation("close"));
                    break;
            }
            iterator.next();
        }
    }

    // Convert PathModel back to Android Path
    public Path toAndroidPath() {
        Path path = new Path();
        path.setFillType(Path.FillType.values()[fillType]);

        for (PathOperation op : operations) {
            switch (op.getType()) {
                case "moveTo":
                    path.moveTo(op.getX(), op.getY());
                    break;
                case "lineTo":
                    path.lineTo(op.getX(), op.getY());
                    break;
                case "quadTo":
                    path.quadTo(op.getX(), op.getY(), op.getCtrlX(), op.getCtrlY());
                    break;
                case "cubicTo":
                    path.cubicTo(op.getX(), op.getY(), op.getCtrlX(), op.getCtrlY(), op.getEndX(), op.getEndY());
                    break;
                case "close":
                    path.close();
                    break;
            }
        }
        return path;
    }

    // Getters and setters (required for Firebase)
    public List<PathOperation> getOperations() { return operations; }
    public void setOperations(List<PathOperation> operations) { this.operations = operations; }
    public int getFillType() { return fillType; }
    public void setFillType(int fillType) { this.fillType = fillType; }
}

// PathOperation stores individual path commands
public class PathOperation {
    private String type;
    private float x, y;
    private float ctrlX, ctrlY;
    private float endX, endY;

    public PathOperation() {}

    // Constructors for different operation types
    public PathOperation(String type) {
        this.type = type;
    }

    public PathOperation(String type, float x, float y) {
        this.type = type;
        this.x = x;
        this.y = y;
    }

    public PathOperation(String type, float x, float y, float ctrlX, float ctrlY) {
        this.type = type;
        this.x = x;
        this.y = y;
        this.ctrlX = ctrlX;
        this.ctrlY = ctrlY;
    }

    public PathOperation(String type, float x, float y, float ctrlX, float ctrlY, float endX, float endY) {
        this.type = type;
        this.x = x;
        this.y = y;
        this.ctrlX = ctrlX;
        this.ctrlY = ctrlY;
        this.endX = endX;
        this.endY = endY;
    }

    // Getters and setters
    public String getType() { return type; }
    public void setType(String type) { this.type = type; }
    public float getX() { return x; }
    public void setX(float x) { this.x = x; }
    // Add getters/setters for other fields...
}

Option B: Simple Point List (For Basic Line Paths)

If your app only draws straight lines, you can simplify things by storing just the list of points in the path. This uses less storage and is easier to implement:

public class SimplePathModel {
    private List<Point> points;
    private int fillType;

    public SimplePathModel() {}

    public SimplePathModel(Path path) {
        this.fillType = path.getFillType().ordinal();
        this.points = new ArrayList<>();

        PathIterator iterator = path.getPathIterator(null);
        float[] coords = new float[2];

        while (!iterator.isDone()) {
            int type = iterator.currentSegment(coords);
            if (type == PathIterator.SEG_MOVETO || type == PathIterator.SEG_LINETO) {
                points.add(new Point(coords[0], coords[1]));
            }
            iterator.next();
        }
    }

    public Path toAndroidPath() {
        Path path = new Path();
        path.setFillType(Path.FillType.values()[fillType]);

        if (!points.isEmpty()) {
            Point first = points.get(0);
            path.moveTo(first.getX(), first.getY());

            for (int i = 1; i < points.size(); i++) {
                Point p = points.get(i);
                path.lineTo(p.getX(), p.getY());
            }
        }
        return path;
    }

    // Getters and setters
    public List<Point> getPoints() { return points; }
    public void setPoints(List<Point> points) { this.points = points; }
    public int getFillType() { return fillType; }
    public void setFillType(int fillType) { this.fillType = fillType; }
}

public class Point {
    private float x, y;

    public Point() {}
    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }

    // Getters and setters
    public float getX() { return x; }
    public void setX(float x) { this.x = x; }
    public float getY() { return y; }
    public void setY(float y) { this.y = y; }
}

2. Structure Your Firebase Keys

The key structure depends on whether you're building a single-user app or a collaborative multi-user app:

For Collaborative Multi-User Drawing

Use Firebase's push() method to generate unique, auto-incrementing keys for each path. This ensures no conflicts when multiple users add paths at the same time. Your Firebase structure might look like this:

drawings
  - {drawingId}  // Generated via push() or custom ID for each drawing
    - paths
      - -Nabc123: { operations: [...], fillType: 0 }  // Path 1
      - -Ndef456: { operations: [...], fillType: 0 }  // Path 2

To add a path:

DatabaseReference pathsRef = FirebaseDatabase.getInstance()
        .getReference("drawings/" + drawingId + "/paths");

// Convert your Android Path to PathModel
PathModel pathModel = new PathModel(yourCanvasPath);

// Push to generate a unique key and save the path
String pathKey = pathsRef.push().getKey();
pathsRef.child(pathKey).setValue(pathModel);

For Single-User Undo/Redo

Even for single-user apps, avoid using numeric indexes as keys (Firebase will reorder indexes if you delete a middle element). Instead, still use push() keys, and maintain a local List<String> to track the order of path keys. This makes undoing the last path straightforward:

// Local variables to track paths and their Firebase keys
private ArrayList<Path> localPaths = new ArrayList<>();
private ArrayList<String> pathKeys = new ArrayList<>();
private DatabaseReference userPathsRef;

// Initialize the reference (replace with your user ID logic)
userPathsRef = FirebaseDatabase.getInstance()
        .getReference("userDrawings/" + currentUserId + "/paths");

// When adding a new path:
public void addPath(Path newPath) {
    localPaths.add(newPath);
    PathModel pathModel = new PathModel(newPath);
    String newKey = userPathsRef.push().getKey();
    pathKeys.add(newKey);
    userPathsRef.child(newKey).setValue(pathModel);
}

// When undoing the last path:
public void undoLastPath() {
    if (!localPaths.isEmpty() && !pathKeys.isEmpty()) {
        // Remove from local list
        localPaths.remove(localPaths.size() - 1);
        // Remove from Firebase
        String lastKey = pathKeys.remove(pathKeys.size() - 1);
        userPathsRef.child(lastKey).removeValue();
    }
}

3. Sync Local and Firebase State

To keep your Canvas in sync with Firebase, add a listener to the paths reference. When a path is added or removed, update your local ArrayList<Path> and redraw the Canvas:

userPathsRef.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) {
        PathModel pathModel = snapshot.getValue(PathModel.class);
        if (pathModel != null) {
            Path path = pathModel.toAndroidPath();
            localPaths.add(path);
            // Trigger Canvas redraw
            yourCanvasView.invalidate();
        }
    }

    @Override
    public void onChildRemoved(@NonNull DataSnapshot snapshot) {
        // Find and remove the corresponding path from local list
        String removedKey = snapshot.getKey();
        int index = pathKeys.indexOf(removedKey);
        if (index != -1) {
            localPaths.remove(index);
            pathKeys.remove(index);
            yourCanvasView.invalidate();
        }
    }

    // Implement other ChildEventListener methods as needed...
});

内容的提问来源于stack exchange,提问作者WHOATEMYNOODLES

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:27:51