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;
}
}
}
这里增加了个方法,点也增加了两个.
这是我在作者的基础上作的修改,把保存笔记,恢复笔记都做了增强,并且升级了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里面的标注.