简单笔画轨迹的绘制,并通过保存轨迹点,实现Path的保存和恢复。

时间:2022-03-06 04:45:10

保存画板中绘制的轨迹,有两种方法:

1.给canvas设置Bitmap,将轨迹等绘制在Bitmap上,在保存图片即可;下次重新进入模块时,加载图片到Bitmap,再通过canvas绘制出来即可。

2.只保存轨迹点,下次进入进入时重新绘制;


方法1,当图片很大时,容易出现OOM异常,这个很难避免。而方法2,可以避免OOM的问题。


下面的代码是按照方法2来实现的,

工程源码:

http://download.csdn.net/detail/victoryckl/4519210

效果图:

简单笔画轨迹的绘制,并通过保存轨迹点,实现Path的保存和恢复。


绘制时通过在onTouchEvent()中,记录触摸点,生成Path,在onDraw()绘制即可。

package org.ckl.path;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.widget.ImageView;

public class MyView extends ImageView {

private static final String TAG = "MyView";
private List<PathAndPaint> mPaths = new ArrayList<PathAndPaint>();//保存每条轨迹的Path和Paint,便于绘制,不可序列化
private Path mPath = new Path();
private Paint mPaint = new Paint();
private PathInfo mPathInfo;//保存每条轨迹的点坐标,可序列化,便于保存到文件

private int[] mColors = new int[]{Color.BLACK, Color.RED, Color.GREEN, Color.BLUE, Color.CYAN, Color.YELLOW};


private void init() {
Log.i(TAG, "init()");
mPaint.setAntiAlias(true);
mPaint.setColor(Color.BLACK);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(3);
}

public MyView(Context context) {
super(context);
init();
}

public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}

public void setPathInfo(PathInfo info) {
mPath = new Path();
mPathInfo = info;
mPaths = mPathInfo.transfer();
invalidate();
}

private int getColor() {
int index = (int) (Math.round(Math.random() * mColors.length) % mColors.length);
return mColors[index];
}

private boolean mHasMove = false;
private float mX,mY;
public boolean onTouchEvent(MotionEvent e) {
boolean ret = false;
switch (e.getAction()) {
case MotionEvent.ACTION_DOWN:
mHasMove = false;
mX = e.getX();
mY = e.getY();
mPaint.setColor(getColor());
mPath.reset();
mPath.moveTo(mX, mY);
if (mPathInfo != null) {
mPathInfo.lineStart(mX, mY);
}
// Log.i(TAG, "mPath.moveTo("+mX+"f,"+mY+"f);");
invalidate();
ret = true;
break;
case MotionEvent.ACTION_UP:
mX = e.getX();
mY = e.getY();
mPath.lineTo(mX, mY);
if (mPathInfo != null && mHasMove) {
mPathInfo.lineEnd(mX, mY, mPaint.getColor());
}
// Log.i(TAG, "mPath.lineTo("+mX+"f,"+mY+"f);");
mPaths.add(new PathAndPaint(new Path(mPath), new Paint(mPaint)));
invalidate();
ret = true;
break;
case MotionEvent.ACTION_MOVE:
mHasMove = true;
float x = e.getX();
float y = e.getY();
mPath.quadTo(mX, mY, (mX + x)/2, (mY + y)/2);
if (mPathInfo != null) {
mPathInfo.lineMove(x, y);
}
// Log.i(TAG, "mPath.quadTo("+mX+"f,"+mY+"f,"+(mX + x)/2+"f,"+(mY + y)/2+"f);");
mX = x;
mY = y;
ret = true;
invalidate();
break;
default:
ret = super.onTouchEvent(e);
break;
}
return ret;
}

protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (PathAndPaint pp : mPaths) {
canvas.drawPath(pp.getPath(), pp.getPaint());
}
canvas.drawPath(mPath, mPaint);
}
}

在android中,android.graphics.Path是不可序列化的,所以不能直接通过ObjectOutputStream保存。

这里用PathAndPaint保存每条轨迹的Path和Paint,便于绘制,不可序列化,PathAndPaint实现如下:

package org.ckl.path;

import android.graphics.Paint;
import android.graphics.Path;

public class PathAndPaint {
private Path mPath;
private Paint mPaint;

public PathAndPaint(Path path, Paint paint) {
mPath = path;
mPaint = paint;
}
public Path getPath() {
return mPath;
}
public Paint getPaint() {
return mPaint;
}
}


PathInfo是轨迹的可以序列化表示,为每条轨迹保存颜色和一系列的坐标点,并实现保存到文件,从文件加载,及通过这些信息恢复PathAndPaint的功能:

package org.ckl.path;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.io.StreamCorruptedException;
import java.util.ArrayList;
import java.util.List;

import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.Log;

public class PathInfo implements Serializable {
private static final long serialVersionUID = -5568568529548959041L;
private static final String TAG = "PathInfo";

class SerPoint implements Serializable {
private static final long serialVersionUID = -2262755099592284491L;

private float x;
private float y;

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

class SerPath implements Serializable {
private static final long serialVersionUID = -900016536427010833L;
private int mColor = Color.BLACK;
private List<SerPoint> mPoints = new ArrayList<SerPoint>();
}

List<SerPath> mSerPaths = new ArrayList<PathInfo.SerPath>();

private SerPath mCurPath;
public void lineStart(float x, float y) {
mCurPath = new SerPath();
mCurPath.mPoints.add(new SerPoint(x, y));
}
public void lineMove(float x, float y) {
mCurPath.mPoints.add(new SerPoint(x, y));
}
public void lineEnd(float x, float y, int color) {
mCurPath.mPoints.add(new SerPoint(x, y));
mCurPath.mColor = color;
mSerPaths.add(mCurPath);
}


private PathInfo() {}
//-转换为 PathAndPaint -------------------------------
private Paint transferPaint(SerPath sp) {
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
paint.setColor(sp.mColor);
return paint;
}
private Path transferPath(SerPath sp) {
Path path = new Path();
SerPoint p;
int size = sp.mPoints.size();

if (size < 3) {
return path;
}

p = sp.mPoints.get(0);
path.moveTo(p.x, p.y);

float ox = p.x;
float oy = p.y;

for (int i = 1; i < size-1; i++) {
p = sp.mPoints.get(i);
path.quadTo(ox, oy, (ox + p.x)/2, (oy + p.y)/2);
ox = p.x;
oy = p.y;
}

p = sp.mPoints.get(size-1);
path.lineTo(p.x, p.y);

return path;
}
public List<PathAndPaint> transfer() {
List<PathAndPaint> pps = new ArrayList<PathAndPaint>();
// Log.i(TAG, "mSerPaths.size() = " + mSerPaths.size());
for (SerPath sp : mSerPaths) {
Paint paint = transferPaint(sp);
Path path = transferPath(sp);
pps.add(new PathAndPaint(path, paint));
}
return pps;
}

//-加载、保存、清空轨迹------------------------------
private static String mSavePath = "/sdcard/.pathinfo";
public static PathInfo load() {
PathInfo pi = null;
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(mSavePath));
pi = (PathInfo)ois.readObject();
Log.i(TAG, "load ok, size = " + pi.mSerPaths.size());
} catch (StreamCorruptedException e) {
e.printStackTrace();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (ois != null) {
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
ois = null;
}
if (pi == null) {
pi = new PathInfo();
}
}
return pi;
}

public void save() {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(new FileOutputStream(mSavePath));
oos.writeObject(this);
Log.i(TAG, "save ok, size = " + mSerPaths.size());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oos != null) {
try {
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
oos = null;
}
}
}

public void clean() {
File f = new File(mSavePath);
if (f.exists()) {
f.delete();
}
mSerPaths = new ArrayList<PathInfo.SerPath>();
}
}


package org.ckl.path;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class SavePathActivity extends Activity {
private PathInfo mPathInfo;
private MyView mMyView;
private Button mClean, mBack;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

mPathInfo = PathInfo.load();

mMyView = (MyView)findViewById(R.id.myview);
mMyView.setPathInfo(mPathInfo);

mClean = (Button)findViewById(R.id.clean);
mClean.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mPathInfo.clean();
mMyView.setPathInfo(mPathInfo);
}
});

mBack = (Button)findViewById(R.id.back);
mBack.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
SavePathActivity.this.finish();
}
});
}

protected void onDestroy() {
if (mPathInfo != null) {
mPathInfo.save();
mPathInfo = null;
}
super.onDestroy();
}
}


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >

<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_weight="1"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:text="@string/hello" />
<Button
android:id="@+id/clean"
android:text="clean"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/back"
android:text="back"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<org.ckl.path.MyView
android:id="@+id/myview"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:src="@drawable/mj"
android:background="@android:color/white"/>
</LinearLayout>