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

相关推荐
匆忙拥挤repeat16 分钟前
Android Compose 渲染 UI 帧的三个阶段:组合、布局、绘制
android·ui
帅得不敢出门28 分钟前
Android Studio同一个工程根据不同芯片平台加载不同的framework.jar及使用不同的代码
android·android studio·jar
xiangxiongfly91542 分钟前
Android LeakCanary源码分析
android·leakcanary
黄林晴42 分钟前
紧急预警!Android 17 定位权限大改,你的 App 要适配了
android
夏沫琅琊1 小时前
Android API 发送短信技术文档
android·kotlin
周周不一样1 小时前
Android基础笔记1
android·笔记·gitee
这辈子谁会真的心疼你1 小时前
怎么修改pdf文档属性?介绍三个方法
数据库·pdf·c#
取码网2 小时前
影视APP源码 SK影视 安卓+苹果双端APP 反编译详细视频教程+源码
android
SEO-狼术2 小时前
Unlock Powerful PDF Editing in SharePoint
pdf·sharepoint