android pdf框架-13,涂鸦,笔记

https://github.com/Linccy/Graffiti 感谢作者的开源的涂鸦库.

基于此项目作的修改与优化.添加保存笔记功能.稍后会移植到pdf中.

涂鸦功能,作者已经完成了,那么笔记的保存就比较重要了.

path没有直接保存的,所以需要新建一个类,继承它,覆盖三个方法,来记录笔记.

要站在牛人的肩膀上,这是做人做事的非常重要的法则.只要合适,合道德,我们应该参考别人的代码.程序员是这世上最善良的人群,写好文档,就怕别人看不懂.

于是我查了一下保存笔记,多数人都是转发同一篇,至于出处就不知道了,可能是这个吧.android.graphics.Path 的序列化 -- Kevin's blog

path如果只覆盖这两个方法,只能存直线.

首先我们从画path开始,先moveto,移到一个点,然后lineto,或者quadto连续画.上面链接的代码只覆盖moveto,lineto只能处理直线笔记.

再看涂鸦,里面的move事件是用的quadto,所以我们需要在序列化点的基础上修改.

直接上代码:

package org.linccy.graffiti;

import android.graphics.Path;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * @author: archko 2024/8/7 :16:38
 */
public class CustomPath extends Path implements Serializable {

    private static final long serialVersionUID = -5974912367682897467L;

    private List<PathAction> actions = new ArrayList<>();

    public List<PathAction> getActions() {
        return actions;
    }

    public void setActions(List<PathAction> actions) {
        this.actions = actions;
        if (this.actions == null) {
            this.actions = new ArrayList<>();
        }
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        drawThisPath();
    }

    @Override
    public void moveTo(float x, float y) {
        actions.add(new ActionMove(x, y));
        super.moveTo(x, y);
    }

    @Override
    public void lineTo(float x, float y) {
        actions.add(new ActionLine(x, y));
        super.lineTo(x, y);
    }

    @Override
    public void quadTo(float x1, float y1, float x2, float y2) {
        actions.add(new ActionQuad(x1, y1, x2, y2));
        super.quadTo(x1, y1, x2, y2);
    }

//这个方法应该在存的时候 保证move是第一个,因为如果不是第一个,就是没有起始点,画不出线的
    public void drawThisPath() {
        PathAction p;
        for (int i = 0; i < actions.size(); i++) {
            p = actions.get(i);
            if (p.getType().equals(PathAction.PathActionType.MOVE_TO)) {
                super.moveTo(p.getX(), p.getY());
            } else if (p.getType().equals(PathAction.PathActionType.LINE_TO)) {
                super.lineTo(p.getX(), p.getY());
            } else if (p.getType().equals(PathAction.PathActionType.QUAD_TO)) {
                super.quadTo(p.getX(), p.getY(), p.getX2(), p.getY2());
            }
        }
    }

    public interface PathAction {
        enum PathActionType {LINE_TO, MOVE_TO, QUAD_TO}

        PathActionType getType();

        float getX();

        float getY();

        float getX2();

        float getY2();
    }

    public static class ActionMove implements PathAction, Serializable {
        private static final long serialVersionUID = -7198142191254133295L;

        private final float x;
        private final float y;

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

        @Override
        public PathActionType getType() {
            return PathActionType.MOVE_TO;
        }

        @Override
        public float getX() {
            return x;
        }

        @Override
        public float getY() {
            return y;
        }

        @Override
        public float getX2() {
            return 0;
        }

        @Override
        public float getY2() {
            return 0;
        }
    }

    public static class ActionLine implements PathAction, Serializable {
        private static final long serialVersionUID = 8307137961494172589L;

        private final float x;
        private final float y;

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

        @Override
        public PathActionType getType() {
            return PathActionType.LINE_TO;
        }

        @Override
        public float getX() {
            return x;
        }

        @Override
        public float getY() {
            return y;
        }

        @Override
        public float getX2() {
            return 0;
        }

        @Override
        public float getY2() {
            return 0;
        }
    }

    public static class ActionQuad implements PathAction, Serializable {
        private static final long serialVersionUID = 8307137961494172589L;

        private final float x1;
        private final float y1;
        private final float x2;
        private final float y2;

        public ActionQuad(float x1, float y1, float x2, float y2) {
            this.x1 = x1;
            this.y1 = y1;
            this.x2 = x2;
            this.y2 = y2;
        }

        @Override
        public PathActionType getType() {
            return PathActionType.QUAD_TO;
        }

        @Override
        public float getX() {
            return x1;
        }

        @Override
        public float getY() {
            return y1;
        }

        @Override
        public float getX2() {
            return x2;
        }

        @Override
        public float getY2() {
            return y2;
        }
    }
}

这里增加了个方法,点也增加了两个.

https://github.com/archko/Graffiti/blob/main/graffiti/src/main/java/org/linccy/graffiti/CustomPath.java

这是我在作者的基础上作的修改,把保存笔记,恢复笔记都做了增强,并且升级了gradle.

剩下的就是保存笔记的问题了

原作者是保存在图片上,我想在pdf上画,我不想保存在图片上面,所以我保存在json的串里面,可以随时恢复.因为我不想编辑pdf,pdf是一个成品,不建议编辑.

增加一个恢复的方法:

private static MarkPath restoreMarkPath(CustomPath path, int color, PointF point) {
            MarkPath newPath = new MarkPath();
            newPath.mPath = path;
            newPath.mPrevPoint = point;

            newPath.sPaint = new Paint();
            newPath.sPaint.setAntiAlias(true);
            newPath.sPaint.setDither(true);
            newPath.sPaint.setStyle(Paint.Style.STROKE);
            newPath.sPaint.setStrokeJoin(Paint.Join.ROUND);
            newPath.sPaint.setStrokeCap(Paint.Cap.ROUND);
            newPath.sPaint.setColor(color);
            return newPath;
        }

然后就是两个方法,一个是保存,一个是恢复,使用json存储.

public static String toJson(ArrayList<MarkPath> finishedPaths) {
            JSONObject object = new JSONObject();
            try {
                object.put("page", 0);
                object.put("ver", "1");
                JSONArray ja = new JSONArray();
                object.put("p", ja);
                for (MarkPath markPath : finishedPaths) {
                    JSONObject obj = new JSONObject();
                    obj.put("t", markPath.mCurrentMarkType.name());
                    obj.put("s", markPath.mCurrentWidth);
                    obj.put("c", markPath.sPaint.getColor());
                    List<CustomPath.PathAction> actions = markPath.mPath.getActions();
                    if (null != actions && !actions.isEmpty()) {
                        JSONArray actionJa = new JSONArray();
                        obj.put("acs", actionJa);
                        for (CustomPath.PathAction action : actions) {
                            JSONObject actionObj = new JSONObject();
                            actionObj.put("x1", action.getX());
                            actionObj.put("y1", action.getY());
                            actionObj.put("x2", action.getX2());
                            actionObj.put("y2", action.getY2());
                            actionObj.put("t", action.getType().name());
                            actionJa.put(actionObj);
                        }
                    }
                    ja.put(obj);
                }
            } catch (JSONException e) {
                Log.e("TAG", e.getMessage());
            }
            String rs = object.toString();
            Log.d("TAG", "toJson:" + rs);
            return rs;
        }

        public static ArrayList<MarkPath> fromJson(String json) {
            Log.d("TAG", "fromJson:" + json);
            ArrayList<MarkPath> paths = new ArrayList<>();
            try {
                JSONObject object = new JSONObject(json);
                JSONArray ja = object.optJSONArray("p");
                for (int i = 0; i < ja.length(); i++) {
                    JSONObject obj = ja.optJSONObject(i);
                    String type = obj.optString("t");
                    int stroke = obj.optInt("s");
                    int color = obj.optInt("c");
                    CustomPath customPath = null;
                    JSONArray actionJa = obj.optJSONArray("acs");
                    PointF point = null;
                    if (null != actionJa && actionJa.length() > 0) {
                        customPath = new CustomPath();
                        List<CustomPath.PathAction> actions = new ArrayList<>();
                        customPath.setActions(actions);
                        for (int j = 0; j < actionJa.length(); j++) {
                            JSONObject actionObj = actionJa.optJSONObject(j);
                            float x1 = (float) actionObj.optDouble("x1");
                            float y1 = (float) actionObj.optDouble("y1");
                            float x2 = (float) actionObj.optDouble("x2");
                            float y2 = (float) actionObj.optDouble("y2");
                            String t = actionObj.optString("t");
                            CustomPath.PathAction.PathActionType actionType = CustomPath.PathAction.PathActionType.valueOf(t);
                            if (actionType == CustomPath.PathAction.PathActionType.MOVE_TO && point == null) {
                                point = new PointF(x1, y1);
                            }
                            if (actionType == CustomPath.PathAction.PathActionType.LINE_TO) {
                                CustomPath.PathAction action = new CustomPath.ActionLine(x1, y1);
                                actions.add(action);
                            } else if (actionType == CustomPath.PathAction.PathActionType.QUAD_TO) {
                                CustomPath.PathAction action = new CustomPath.ActionQuad(x1, y1, x2, y2);
                                actions.add(action);
                            } else if (actionType == CustomPath.PathAction.PathActionType.MOVE_TO) {
                                CustomPath.PathAction action = new CustomPath.ActionMove(x1, y1);
                                actions.add(action);
                            }
                        }
                        customPath.drawThisPath();
                    }
                    MarkPath markPath = restoreMarkPath(customPath, color, point);
                    markPath.mCurrentWidth = stroke;
                    markPath.mCurrentMarkType = MarkType.valueOf(type);
                    paths.add(markPath);
                }
            } catch (JSONException e) {
                Log.e("TAG", e.getMessage());
            }
            return paths;
        }

剩下绘制这些代码不变.key简化了,因为涂鸦在一个图片上可能会产生很多点,尤其不是画直线,是画曲线,点的数量比较可观,尽量减少整个点的存储量.画一笔可能几十个点.

除了这些修改,还修改了画笔的颜色设置,去除原来的一些设置这样view只作画,不作其它业务用.后面打算这个涂鸦工程再优化一下,完善一些功能.然后移植去pdf里面的标注.

相关推荐
诸神黄昏EX4 分钟前
Android 分区相关介绍
android
慧都小妮子40 分钟前
Spire.PDF for .NET【页面设置】演示:打开 PDF 时自动显示书签或缩略图
java·pdf·.net
大白要努力!1 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee1 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood1 小时前
Perfetto学习大全
android·性能优化·perfetto
join83 小时前
解决vue-pdf的签章不显示问题
javascript·vue.js·pdf
小行星1254 小时前
前端把dom页面转为pdf文件下载和弹窗预览
前端·javascript·vue.js·pdf
Dnelic-4 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen6 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年14 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin