提交 de52067d 编写于 作者: H huangziwei

optimize drawing

上级 600def7d
......@@ -6,6 +6,8 @@ Image doodle for Android. You can undo, zoom, move, add text, textures, etc. Als
***Android图片涂鸦,具有撤消、缩放、移动、添加文字,贴图等功能。还是一个功能强大,可自定义和可扩展的涂鸦框架、多功能画板。***
![01.gif](https://raw.githubusercontent.com/1993hzw/common/master/Doodle/01.gif)
![01](https://raw.githubusercontent.com/1993hzw/common/master/Doodle/01.png)
......@@ -60,7 +62,7 @@ There are two ways to use the Doodle library:
***这里有两种方式使用Doodle涂鸦库***
* A. Launch DoodleActivity directly (the layout is like demo images above). If you need to customize more interactions, please use another method (Way B).
***使用写好的涂鸦界面,直接启动.启动的页面可参看上面的演示图片。如果需要自定义更多的交互方式,则请使用另一种方式(即B方式)。***
```java
DoodleParams params = new DoodleParams(); // 涂鸦参数
......@@ -72,10 +74,17 @@ See [DoodleParams](https://github.com/1993hzw/Doodle/blob/master/doodle/src/main
***查看[DoodleParams](https://github.com/1993hzw/Doodle/blob/master/doodle/src/main/java/cn/hzw/doodle/DoodleParams.java)获取更多涂鸦参数信息。***
* B. Recommend, use DoodleView and customize your layout.
***推荐的方法:使用DoodleView,便于拓展,灵活性高,自定义自己的交互界面.***
mDoodle
***推荐的方法:使用DoodleView,便于拓展,灵活性高,自定义自己的交互界面.***
```java
DoodleView mDoodle = mDoodleView = new DoodleView(this, bitmap, new IDoodleListener() {
/*
Whether or not to optimize drawing, it is suggested to open, which can optimize the drawing speed and performance.
Note: When item is selected for editing after opening, it will be drawn at the top level, and not at the corresponding level until editing is completed.
是否优化绘制,建议开启,可优化绘制速度和性能.
注意:开启后item被选中编辑时时会绘制在最上面一层,直到结束编辑后才绘制在相应层级
*/
boolean optimizeDrawing = true;
DoodleView mDoodleView = new DoodleView(this, bitmap, optimizeDrawing, new IDoodleListener() {
/*
called when save the doodled iamge.
保存涂鸦图像时调用
......@@ -113,12 +122,12 @@ mTouchGestureListener = new DoodleOnTouchGestureListener(mDoodleView, new Doodle
public void onCreateSelectableItem(IDoodle doodle, float x, float y) {
//do something
/*
if (mDoodle.getPen() == DoodlePen.TEXT) {
IDoodleSelectableItem item = new DoodleText(mDoodle, "hello", 20 * mDoodle.getUnitSize(), new DoodleColor(Color.RED), x, y);
mDoodle.addItem(item);
} else if (mDoodle.getPen() == DoodlePen.BITMAP) {
IDoodleSelectableItem item = new DoodleBitmap(mDoodle, bitmap, 80 * mDoodle.getUnitSize(), x, y);
mDoodle.addItem(item);
if (mDoodleView.getPen() == DoodlePen.TEXT) {
IDoodleSelectableItem item = new DoodleText(mDoodleView, "hello", 20 * mDoodleView.getUnitSize(), new DoodleColor(Color.RED), x, y);
mDoodleView.addItem(item);
} else if (mDoodleView.getPen() == DoodlePen.BITMAP) {
IDoodleSelectableItem item = new DoodleBitmap(mDoodleView, bitmap, 80 * mDoodle.getUnitSize(), x, y);
mDoodleView.addItem(item);
}
*/
}
......
......@@ -184,7 +184,13 @@ public class DoodleActivity extends Activity {
setContentView(R.layout.doodle_layout);
mFrameLayout = (FrameLayout) findViewById(R.id.doodle_container);
mDoodle = mDoodleView = new DoodleViewWrapper(this, bitmap, new IDoodleListener() {
/*
Whether or not to optimize drawing, it is suggested to open, which can optimize the drawing speed and performance.
Note: When item is selected for editing after opening, it will be drawn at the top level, and not at the corresponding level until editing is completed.
是否优化绘制,建议开启,可优化绘制速度和性能.
注意:开启后item被选中编辑时时会绘制在最上面一层,直到结束编辑后才绘制在相应层级
*/
mDoodle = mDoodleView = new DoodleViewWrapper(this, bitmap, mDoodleParams.mOptimizeDrawing, new IDoodleListener() {
@Override
public void onSaved(IDoodle doodle, Bitmap bitmap, Runnable callback) { // 保存图片为jpg格式
File doodleFile = null;
......@@ -614,7 +620,7 @@ public class DoodleActivity extends Activity {
} else if (v.getId() == R.id.doodle_btn_finish) {
mDoodle.save();
} else if (v.getId() == R.id.doodle_btn_back) {
if (mDoodle.getAllItem() == null || mDoodle.getAllItem().size() == 0) {
if (mDoodle.getAllItem() == null || mDoodle.getItemCount() == 0) {
finish();
return;
}
......@@ -759,12 +765,8 @@ public class DoodleActivity extends Activity {
*/
private class DoodleViewWrapper extends DoodleView {
public DoodleViewWrapper(Context context, Bitmap bitmap, IDoodleListener listener) {
super(context, bitmap, listener);
}
public DoodleViewWrapper(Context context, Bitmap bitmap, IDoodleListener listener, IDoodleTouchDetector defaultDetector) {
super(context, bitmap, listener, defaultDetector);
public DoodleViewWrapper(Context context, Bitmap bitmap, boolean optimizeDrawing, IDoodleListener listener, IDoodleTouchDetector defaultDetector) {
super(context, bitmap, optimizeDrawing, listener, defaultDetector);
}
private Map<IDoodlePen, Integer> mBtnPenIds = new HashMap<>();
......
......@@ -73,12 +73,14 @@ public class DoodleOnTouchGestureListener extends TouchGestureDetector.OnTouchGe
if (mSelectionListener != null) {
mSelectionListener.onSelectedItem(mDoodle, old, false);
}
mDoodle.notifyItemFinishedDrawing(old);
}
if (mSelectedItem != null) {
mSelectedItem.setSelected(true);
if (mSelectionListener != null) {
mSelectionListener.onSelectedItem(mDoodle, mSelectedItem, true);
}
mDoodle.markItemToOptimizeDrawing(mSelectedItem);
}
}
......@@ -145,7 +147,11 @@ public class DoodleOnTouchGestureListener extends TouchGestureDetector.OnTouchGe
mCurrDoodlePath = DoodlePath.toShape(mDoodle,
mDoodle.toX(mTouchDownX), mDoodle.toY(mTouchDownY), mDoodle.toX(mTouchX), mDoodle.toY(mTouchY));
}
mDoodle.addItem(mCurrDoodlePath);
if (mDoodle.isOptimizeDrawing()) {
mDoodle.markItemToOptimizeDrawing(mCurrDoodlePath);
} else {
mDoodle.addItem(mCurrDoodlePath);
}
}
}
mDoodle.refresh();
......@@ -166,11 +172,13 @@ public class DoodleOnTouchGestureListener extends TouchGestureDetector.OnTouchGe
if (mDoodle.isEditMode()) {
limitBound(true);
}
} else {
if (mCurrDoodlePath != null) {
mCurrDoodlePath = null;
}
}
if (mCurrDoodlePath != null) {
mDoodle.notifyItemFinishedDrawing(mCurrDoodlePath);
mCurrDoodlePath = null;
}
mDoodle.refresh();
}
......
......@@ -78,6 +78,14 @@ public class DoodleParams implements Parcelable {
*/
public boolean mSupportScaleItem = true;
/**
*
* 是否优化绘制,开启后涂鸦会及时绘制在图片上,以此优化绘制速度和性能.
*
* {@link DoodleView#mOptimizeDrawing}
*/
public boolean mOptimizeDrawing = true;
public static final Creator<DoodleParams> CREATOR = new Creator<DoodleParams>() {
@Override
public DoodleParams createFromParcel(Parcel in) {
......@@ -95,6 +103,7 @@ public class DoodleParams implements Parcelable {
params.mMaxScale = in.readFloat();
params.mPaintColor = in.readInt();
params.mSupportScaleItem = in.readInt() == 1;
params.mOptimizeDrawing = in.readInt() == 1;
return params;
}
......@@ -120,6 +129,7 @@ public class DoodleParams implements Parcelable {
dest.writeFloat(mMaxScale);
dest.writeInt(mPaintColor);
dest.writeInt(mSupportScaleItem ? 1 : 0);
dest.writeInt(mOptimizeDrawing ? 1 : 0);
}
......
package cn.hzw.doodle;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
......@@ -9,14 +10,15 @@ import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.AsyncTask;
import android.os.Looper;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import cn.forward.androids.utils.ImageUtils;
import cn.forward.androids.utils.Util;
......@@ -44,9 +46,12 @@ public class DoodleView extends View implements IDoodle {
public static final int ERROR_INIT = -1;
public static final int ERROR_SAVE = -2;
private static final int FLAG_REFRESH_DOODLEE_BITMAP = 1 << 1;
private static final int FLAG_DRAW_TO_DOODLE_BITMAP = 1 << 2;
private IDoodleListener mDoodleListener;
private Bitmap mBitmap; // 当前涂鸦的原图(旋转后)
private final Bitmap mBitmap; // 当前涂鸦的原图
private float mCenterScale; // 图片适应屏幕时的缩放倍数
private int mCenterHeight, mCenterWidth;// 图片适应屏幕时的大小(View窗口坐标系上的大小)
......@@ -69,7 +74,7 @@ public class DoodleView extends View implements IDoodle {
private boolean mReady = false;
// 保存涂鸦操作,便于撤销
private CopyOnWriteArrayList<IDoodleItem> mItemStack = new CopyOnWriteArrayList<IDoodleItem>();
private List<IDoodleItem> mItemStack = new ArrayList<>();
private IDoodlePen mPen;
private IDoodleShape mShape;
......@@ -97,18 +102,43 @@ public class DoodleView extends View implements IDoodle {
private PointF mTempPoint = new PointF();
private boolean mIsEditMode = false; //是否是编辑模式,可移动缩放涂鸦
private boolean mIsSaving = false;
/**
Whether or not to optimize drawing, it is suggested to open, which can optimize the drawing speed and performance.
Note: When item is selected for editing after opening, it will be drawn at the top level, and not at the corresponding level until editing is completed.
是否优化绘制,建议开启,可优化绘制速度和性能.
注意:开启后item被选中编辑时时会绘制在最上面一层,直到结束编辑后才绘制在相应层级
**/
private final boolean mOptimizeDrawing; // 涂鸦及时绘制在图片上,优化性能
private List<IDoodleItem> mItemStackOnViewCanvas = new ArrayList<>(); // 这些item绘制在View的画布上,而不是在图片Bitmap.比如正在制作或选中的item
private List<IDoodleItem> mPendingItemsDrawToBitmap = new ArrayList<>();
private Bitmap mDoodleBitmap;
private int mFlags = 0;
private Canvas mDoodleBitmapCanvas;
public DoodleView(Context context, Bitmap bitmap, IDoodleListener listener) {
this(context, bitmap, listener, null);
this(context, bitmap, false, listener, null);
}
public DoodleView(Context context, Bitmap bitmap, IDoodleListener listener, IDoodleTouchDetector defaultDetector) {
this(context, bitmap, true, listener, defaultDetector);
}
/**
* 如果开启
*
* @param context
* @param bitmap
* @param optimizeDrawing 是否优化绘制,开启后涂鸦会及时绘制在图片上,以此优化绘制速度和性能.
* 如果开启了优化绘制,当绘制或编辑某个item时需要调用 {@link #markItemToOptimizeDrawing(IDoodleItem)},无需再调用{@link #addItem(IDoodleItem)}.
* 另外结束时需要调用对应的 {@link #notifyItemFinishedDrawing(IDoodleItem)}。
* {@link #mOptimizeDrawing}
*
* @param listener
* @param defaultDetector 默认手势识别器
*/
public DoodleView(Context context, Bitmap bitmap, IDoodleListener listener, IDoodleTouchDetector defaultDetector) {
public DoodleView(Context context, Bitmap bitmap, boolean optimizeDrawing, IDoodleListener listener, IDoodleTouchDetector defaultDetector) {
super(context);
// 关闭硬件加速,某些绘图操作不支持硬件加速
......@@ -123,6 +153,8 @@ public class DoodleView extends View implements IDoodle {
throw new RuntimeException("Bitmap is null!!!");
}
mOptimizeDrawing = optimizeDrawing;
mScale = 1f;
mColor = new DoodleColor(Color.RED);
......@@ -151,7 +183,7 @@ public class DoodleView extends View implements IDoodle {
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
initDoodleBitmap();
init();
if (!mReady) {
mDoodleListener.onReady(this);
mReady = true;
......@@ -191,7 +223,7 @@ public class DoodleView extends View implements IDoodle {
super.setOnTouchListener(l);
}
private void initDoodleBitmap() {// 不用resize preview
private void init() {// 不用resize preview
int w = mBitmap.getWidth();
int h = mBitmap.getHeight();
float nw = w * 1f / getWidth();
......@@ -223,9 +255,23 @@ public class DoodleView extends View implements IDoodle {
mTransX = mTransY = 0;
mScale = 1;
initDoodleBitmap();
refresh();
}
private void initDoodleBitmap() {
if (!mOptimizeDrawing) {
return;
}
if (mDoodleBitmap != null) {
mDoodleBitmap.recycle();
}
mDoodleBitmap = mBitmap.copy(mBitmap.getConfig(), true);
mDoodleBitmapCanvas = new Canvas(mDoodleBitmap);
}
/**
* 获取当前图片在View坐标系中的矩型区域
*
......@@ -387,7 +433,29 @@ public class DoodleView extends View implements IDoodle {
}
private boolean hasFlag(int flag) {
return (mFlags & flag) != 0;
}
private void addFlag(int flag) {
mFlags = mFlags | flag;
}
private void clearFlag(int flag) {
mFlags = mFlags & ~flag;
}
private void doDraw(Canvas canvas) {
if (hasFlag(FLAG_REFRESH_DOODLEE_BITMAP)) {
clearFlag(FLAG_REFRESH_DOODLEE_BITMAP);
clearFlag(FLAG_DRAW_TO_DOODLE_BITMAP);
refreshDoodleBitmap(false);
} else if (hasFlag(FLAG_DRAW_TO_DOODLE_BITMAP)) {
clearFlag(FLAG_DRAW_TO_DOODLE_BITMAP);
drawToDoodleBitmap(mPendingItemsDrawToBitmap);
mPendingItemsDrawToBitmap.clear();
}
float left = getAllTranX();
float top = getAllTranY();
......@@ -400,16 +468,24 @@ public class DoodleView extends View implements IDoodle {
canvas.drawBitmap(mBitmap, 0, 0, null);
return;
}
Bitmap bitmap = mOptimizeDrawing ? mDoodleBitmap : mBitmap;
// 绘制涂鸦后的图片
canvas.drawBitmap(mBitmap, 0, 0, null);
canvas.drawBitmap(bitmap, 0, 0, null);
int saveCount = canvas.save(); // 1
List<IDoodleItem> items = mItemStack;
if (mOptimizeDrawing) {
items = mItemStackOnViewCanvas;
}
boolean canvasClipped = false;
canvas.save(); // 1
if (!mIsDrawableOutside) { // 裁剪绘制区域为图片区域
canvasClipped = true;
canvas.clipRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
canvas.clipRect(0, 0, bitmap.getWidth(), bitmap.getHeight());
}
for (IDoodleItem item : mItemStack) {
for (IDoodleItem item : items) {
if (!item.isNeedClipOutside()) { // 1.不需要裁剪
if (canvasClipped) {
canvas.restore();
......@@ -419,7 +495,7 @@ public class DoodleView extends View implements IDoodle {
if (canvasClipped) { // 2.恢复裁剪
canvas.save();
canvas.clipRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
canvas.clipRect(0, 0, bitmap.getWidth(), bitmap.getHeight());
}
} else {
item.draw(canvas);
......@@ -427,7 +503,7 @@ public class DoodleView extends View implements IDoodle {
}
// draw at the top
for (IDoodleItem item : mItemStack) {
for (IDoodleItem item : items) {
if (!item.isNeedClipOutside()) { // 1.不需要裁剪
if (canvasClipped) {
canvas.restore();
......@@ -436,14 +512,13 @@ public class DoodleView extends View implements IDoodle {
if (canvasClipped) { // 2.恢复裁剪
canvas.save();
canvas.clipRect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
canvas.clipRect(0, 0, bitmap.getWidth(), bitmap.getHeight());
}
} else {
item.drawAtTheTop(canvas);
}
}
canvas.restore();
canvas.restoreToCount(saveCount);
if (mPen != null) {
mPen.drawHelpers(canvas, this);
......@@ -561,6 +636,35 @@ public class DoodleView extends View implements IDoodle {
return mDefaultTouchDetector;
}
private void drawToDoodleBitmap(List<IDoodleItem> items) {
if (!mOptimizeDrawing) {
return;
}
for (IDoodleItem item : items) {
item.draw(mDoodleBitmapCanvas);
}
}
private void refreshDoodleBitmap(boolean drawAll) {
if (!mOptimizeDrawing) {
return;
}
initDoodleBitmap();
List<IDoodleItem> items = null;
if (drawAll) {
items = mItemStack;
} else {
items = new ArrayList<>(mItemStack);
items.removeAll(mItemStackOnViewCanvas);
}
for (IDoodleItem item : items) {
item.draw(mDoodleBitmapCanvas);
}
}
// ========================= api ================================
@Override
......@@ -626,26 +730,104 @@ public class DoodleView extends View implements IDoodle {
refresh();
}
public boolean isOptimizeDrawing() {
return mOptimizeDrawing;
}
/**
* 标志item绘制在View的画布上,而不是在图片Bitmap. 比如正在制作或选中的item. 结束绘制时应调用 {@link #notifyItemFinishedDrawing(IDoodleItem)}
* 仅在开启优化绘制(mOptimizeDrawing=true)时生效
*
* @param item
*/
public void markItemToOptimizeDrawing(IDoodleItem item) {
if (!mOptimizeDrawing) {
return;
}
if (mItemStackOnViewCanvas.contains(item)) {
throw new RuntimeException("The item has been added");
}
mItemStackOnViewCanvas.add(item);
if (mItemStack.contains(item)) {
addFlag(FLAG_REFRESH_DOODLEE_BITMAP);
}
refresh();
}
/**
* 把item从View画布中移除并绘制在涂鸦图片上. 对应 {@link #notifyItemFinishedDrawing(IDoodleItem)}
*
* @param item
*/
public void notifyItemFinishedDrawing(IDoodleItem item) {
if (!mOptimizeDrawing) {
return;
}
if (mItemStackOnViewCanvas.remove(item)) {
if (mItemStack.contains(item)) {
addFlag(FLAG_REFRESH_DOODLEE_BITMAP);
} else {
addItem(item);
mPendingItemsDrawToBitmap.add(item);
addFlag(FLAG_DRAW_TO_DOODLE_BITMAP);
}
}
refresh();
}
/**
* 保存, 回调DoodleListener.onSaved()的线程和调用save()的线程相同
*/
@SuppressLint("StaticFieldLeak")
@Override
public void save() {
Bitmap savedBitmap = mBitmap.copy(mBitmap.getConfig(), true);
Canvas canvas = new Canvas(savedBitmap);
for (IDoodleItem item : mItemStack) {
if (item instanceof DoodleItemBase) {
item.draw(canvas);
}
if (mIsSaving) {
return;
}
savedBitmap = ImageUtils.rotate(savedBitmap, mDoodleRotateDegree, true);
mDoodleListener.onSaved(this, savedBitmap, new Runnable() {
mIsSaving = true;
new AsyncTask<Void, Void, Bitmap>() {
@SuppressLint("WrongThread")
@Override
public void run() {
refresh();
protected Bitmap doInBackground(Void... voids) {
Bitmap savedBitmap = null;
if (mOptimizeDrawing) {
refreshDoodleBitmap(true);
savedBitmap = mDoodleBitmap;
} else {
savedBitmap = mBitmap.copy(mBitmap.getConfig(), true);
Canvas canvas = new Canvas(savedBitmap);
for (IDoodleItem item : mItemStack) {
item.draw(canvas);
}
}
savedBitmap = ImageUtils.rotate(savedBitmap, mDoodleRotateDegree, true);
return savedBitmap;
}
});
@Override
protected void onPostExecute(Bitmap bitmap) {
mDoodleListener.onSaved(DoodleView.this, bitmap, new Runnable() {
@Override
public void run() {
if (mOptimizeDrawing) {
refreshDoodleBitmap(false);
}
refresh();
}
});
}
}.execute();
}
/**
......@@ -658,6 +840,9 @@ public class DoodleView extends View implements IDoodle {
item.onRemove();
}
mItemStack.clear();
addFlag(FLAG_REFRESH_DOODLEE_BITMAP);
refresh();
}
......@@ -919,6 +1104,9 @@ public class DoodleView extends View implements IDoodle {
mItemStack.remove(item);
mItemStack.add(item);
addFlag(FLAG_REFRESH_DOODLEE_BITMAP);
refresh();
}
......@@ -930,6 +1118,9 @@ public class DoodleView extends View implements IDoodle {
mItemStack.remove(item);
mItemStack.add(0, item);
addFlag(FLAG_REFRESH_DOODLEE_BITMAP);
refresh();
}
......@@ -975,6 +1166,9 @@ public class DoodleView extends View implements IDoodle {
mItemStack.add(item);
item.onAdd();
mPendingItemsDrawToBitmap.add(item);
addFlag(FLAG_DRAW_TO_DOODLE_BITMAP);
refresh();
}
......@@ -985,12 +1179,19 @@ public class DoodleView extends View implements IDoodle {
}
doodleItem.onRemove();
addFlag(FLAG_REFRESH_DOODLEE_BITMAP);
refresh();
}
@Override
public int getItemCount() {
return mItemStack.size();
}
@Override
public List<IDoodleItem> getAllItem() {
return mItemStack;
return new ArrayList<>(mItemStack);
}
@Override
......
......@@ -175,6 +175,13 @@ public interface IDoodle {
*/
public void removeItem(IDoodleItem doodleItem);
/**
* total item count
*
* @return
*/
public int getItemCount();
/**
* 获取所有的涂鸦
*
......
MIN_SDK_VERSION=14
TARGET_SDK_VERSION=22
VERSION_NAME=5.4.0.2
VERSION_CODE=53
VERSION_NAME=5.4.1
VERSION_CODE=54
COMPILE_SDK_VERSION=27
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册