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

相关推荐
阿巴斯甜16 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker16 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952717 小时前
Andorid Google 登录接入文档
android
黄林晴19 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android