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里面的标注.

相关推荐
dalancon1 小时前
VSYNC 信号流程分析 (Android 14)
android
dalancon1 小时前
VSYNC 信号完整流程2
android
dalancon1 小时前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
优化控制仿真模型1 小时前
2026年最新驾考科目一考试题库2309道全。电子版pdf
经验分享·算法·pdf
用户69371750013842 小时前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android3 小时前
Android 刷新一帧流程trace拆解
android
墨狂之逸才3 小时前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
阿明的小蝴蝶4 小时前
记一次Gradle环境的编译问题与解决
android·前端·gradle
汪海游龙4 小时前
开源项目 Trending AI 招募 Google Play 内测人员(12 名)
android·github
脑电信号要分类5 小时前
将多张图片拼接成一个pdf文件输出
pdf·c#·apache