我正在使用此source code并且运行良好但在这里我无法实现撤消,重做和擦除功能。我也为这种功能创建了类
private static class DrawOp {
public final Path path = new Path();
public Type type;
public int color;
public int stroke;
public DrawOp() {
//
}
public void reset() {
this.path.reset();
}
public DrawOp(DrawOp op) {
this.path.set(op.path);
this.type = op.type;
this.color = op.color;
this.stroke = op.stroke;
}
public static enum Type {
PAINT, ERASE;
}
}
用于橡皮擦
mPaintEraser.set(mPaintColor);
mPaintEraser.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
mPaintEraser.setMaskFilter(new BlurMaskFilter(getResources().getDisplayMetrics().density * 4, BlurMaskFilter.Blur.NORMAL));
我尝试了很多但没有工作请帮助我。 提前谢谢。
// My Drawing full class code
public class SignaturePad extends View {
private static final int DOUBLE_CLICK_DELAY_MS = 200;
private final SvgBuilder mSvgBuilder = new SvgBuilder();
//Default attribute values
private final int DEFAULT_ATTR_PEN_MIN_WIDTH_PX = 3;
private final int DEFAULT_ATTR_PEN_MAX_WIDTH_PX = 7;
private final int DEFAULT_ATTR_PEN_COLOR = Color.BLACK;
private final float DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT = 0.9f;
private final boolean DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK = false;
//View state
private List<TimedPoint> mPoints;
private boolean mIsEmpty;
private float mLastTouchX;
private float mLastTouchY;
private float mLastVelocity;
private float mLastWidth;
private RectF mDirtyRect;
// Cache
private List<TimedPoint> mPointsCache = new ArrayList<>();
private ControlTimedPoints mControlTimedPointsCached = new ControlTimedPoints();
private Bezier mBezierCached = new Bezier();
//Configurable parameters
private int mMinWidth;
private int mMaxWidth;
private float mVelocityFilterWeight;
private OnSignedListener mOnSignedListener;
private boolean mClearOnDoubleClick;
//Click values
private long mFirstClick;
private int mCountClick;
private Paint mPaint = new Paint();
private Bitmap mSignatureBitmap = null;
private Canvas mSignatureBitmapCanvas = null;
// private ArrayList<DrawOp> mDrawOps = new ArrayList<DrawOp>();
// private DrawOp mCurrentOp = new DrawOp();
// private ArrayList<DrawOp> mUndoneOps = new ArrayList<DrawOp>();
// private final Canvas mLayerCanvas = new Canvas();
public SignaturePad(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.SignaturePad,
0, 0);
//Configurable parameters
try {
mMinWidth = a.getDimensionPixelSize(R.styleable.SignaturePad_penMinWidth, convertDpToPx(DEFAULT_ATTR_PEN_MIN_WIDTH_PX));
mMaxWidth = a.getDimensionPixelSize(R.styleable.SignaturePad_penMaxWidth, convertDpToPx(DEFAULT_ATTR_PEN_MAX_WIDTH_PX));
mPaint.setColor(a.getColor(R.styleable.SignaturePad_penColor, DEFAULT_ATTR_PEN_COLOR));
mVelocityFilterWeight = a.getFloat(R.styleable.SignaturePad_velocityFilterWeight, DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT);
mClearOnDoubleClick = a.getBoolean(R.styleable.SignaturePad_clearOnDoubleClick, DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK);
} finally {
a.recycle();
}
//Fixed parameters
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
//Dirty rectangle to update only the changed portion of the view
mDirtyRect = new RectF();
clear();
}
public void setPenColorRes(int colorRes) {
try {
setPenColor(getResources().getColor(colorRes));
} catch (Resources.NotFoundException ex) {
setPenColor(Color.parseColor("#000000"));
}
}
public void setPenColor(int color) {
mPaint.setColor(color);
}
public void setMinWidth(float minWidth) {
mMinWidth = convertDpToPx(minWidth);
}
public void setMaxWidth(float maxWidth) {
mMaxWidth = convertDpToPx(maxWidth);
}
public void setVelocityFilterWeight(float velocityFilterWeight) {
mVelocityFilterWeight = velocityFilterWeight;
}
public void clear() {
mSvgBuilder.clear();
mPoints = new ArrayList<>();
mLastVelocity = 0;
mLastWidth = (mMinWidth + mMaxWidth) / 2;
if (mSignatureBitmap != null) {
mSignatureBitmap = null;
ensureSignatureBitmap();
}
setIsEmpty(true);
invalidate();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled())
return false;
float eventX = event.getX();
float eventY = event.getY();
final float x = event.getX();
final float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// mUndoneOps.clear();
// mCurrentOp.path.moveTo(x, y);
getParent().requestDisallowInterceptTouchEvent(true);
mPoints.clear();
if (isDoubleClick()) break;
mLastTouchX = eventX;
mLastTouchY = eventY;
addPoint(getNewPoint(eventX, eventY));
if (mOnSignedListener != null) mOnSignedListener.onStartSigning();
case MotionEvent.ACTION_MOVE:
// for (int i = 0; i < event.getHistorySize(); i++) {
// mCurrentOp.path.lineTo(event.getHistoricalX(i), event.getHistoricalY(i));
// }
// mCurrentOp.path.lineTo(x, y);
resetDirtyRect(eventX, eventY);
addPoint(getNewPoint(eventX, eventY));
break;
case MotionEvent.ACTION_UP:
resetDirtyRect(eventX, eventY);
addPoint(getNewPoint(eventX, eventY));
getParent().requestDisallowInterceptTouchEvent(true);
setIsEmpty(false);
break;
// case MotionEvent.ACTION_CANCEL:
// mCurrentOp.path.lineTo(x, y);
// mDrawOps.add(new DrawOp(mCurrentOp));
// mCurrentOp.path.reset();
// break;
default:
return false;
}
//invalidate();
invalidate(
(int) (mDirtyRect.left - mMaxWidth),
(int) (mDirtyRect.top - mMaxWidth),
(int) (mDirtyRect.right + mMaxWidth),
(int) (mDirtyRect.bottom + mMaxWidth));
return true;
}
@Override
protected void onDraw(Canvas canvas) {
if (mSignatureBitmap != null) {
canvas.drawBitmap(mSignatureBitmap, 0, 0, mPaint);
}
// mLayerCanvas.drawColor(0, PorterDuff.Mode.CLEAR);
// for (DrawOp op : mDrawOps) {
// drawOp(mLayerCanvas, op);
// }
// drawOp(mLayerCanvas, mCurrentOp);
}
public void setOnSignedListener(OnSignedListener listener) {
mOnSignedListener = listener;
}
public boolean isEmpty() {
return mIsEmpty;
}
public String getSignatureSvg() {
int width = getTransparentSignatureBitmap().getWidth();
int height = getTransparentSignatureBitmap().getHeight();
return mSvgBuilder.build(width, height);
}
public Bitmap getSignatureBitmap() {
Bitmap originalBitmap = getTransparentSignatureBitmap();
Bitmap whiteBgBitmap = Bitmap.createBitmap(originalBitmap.getWidth(), originalBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(whiteBgBitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(originalBitmap, 0, 0, null);
return whiteBgBitmap;
}
public void setSignatureBitmap(final Bitmap signature) {
// View was laid out...
if (ViewCompat.isLaidOut(this)) {
clear();
ensureSignatureBitmap();
RectF tempSrc = new RectF();
RectF tempDst = new RectF();
int dWidth = signature.getWidth();
int dHeight = signature.getHeight();
int vWidth = getWidth();
int vHeight = getHeight();
// Generate the required transform.
tempSrc.set(0, 0, dWidth, dHeight);
tempDst.set(0, 0, vWidth, vHeight);
Matrix drawMatrix = new Matrix();
drawMatrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.CENTER);
Canvas canvas = new Canvas(mSignatureBitmap);
canvas.drawBitmap(signature, drawMatrix, null);
setIsEmpty(false);
invalidate();
}
// View not laid out yet e.g. called from onCreate(), onRestoreInstanceState()...
else {
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// Remove layout listener...
ViewTreeObserverCompat.removeOnGlobalLayoutListener(getViewTreeObserver(), this);
// Signature bitmap...
setSignatureBitmap(signature);
}
});
}
}
/**
* @param compressPercentage Hint to the compressor, 0-100 percent. 0 meaning compress for
* small size, 100 meaning compress for max quality. Some
* formats, like PNG which is lossless, will ignore the
* quality setting
*/
public Bitmap getCompressedSignatureBitmap(int compressPercentage) {
if (compressPercentage < 0) {
compressPercentage = 0;
} else if (compressPercentage > 100) {
compressPercentage = 100;
}
Bitmap originalBitmap = getTransparentSignatureBitmap();
int originalWidth = originalBitmap.getWidth();
int originalHeight = originalBitmap.getHeight();
int targetWidth = originalWidth * compressPercentage / 100; // your arbitrary fixed limit
int targetHeight = (int) (originalHeight * targetWidth / (double) originalWidth);
Bitmap whiteBgBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(whiteBgBitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(originalBitmap, 0, 0, null);
whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true);
return whiteBgBitmap;
}
/**
* @param deiredWidth Desired width of the bitmap
*/
public Bitmap getFixedSizeSignatureBitmap(int deiredWidth) {
Bitmap originalBitmap = getTransparentSignatureBitmap();
int originalWidth = originalBitmap.getWidth();
int originalHeight = originalBitmap.getHeight();
int targetWidth = deiredWidth; // your arbitrary fixed limit
int targetHeight = (int) (originalHeight * targetWidth / (double) originalWidth);
Bitmap whiteBgBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(whiteBgBitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(originalBitmap, 0, 0, null);
whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true);
return whiteBgBitmap;
}
/**
* @param deiredWidth Desired width of the bitmap
*/
public Bitmap getFixedSizeSignatureBitmap(int deiredWidth, int desiredHeight) {
Bitmap originalBitmap = getTransparentSignatureBitmap();
int originalWidth = originalBitmap.getWidth();
int originalHeight = originalBitmap.getHeight();
int targetWidth = deiredWidth; // your arbitrary fixed limit
int targetHeight = desiredHeight;
Bitmap whiteBgBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(whiteBgBitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(originalBitmap, 0, 0, null);
whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true);
return whiteBgBitmap;
}
public Bitmap getTransparentSignatureBitmap() {
ensureSignatureBitmap();
return mSignatureBitmap;
}
public Bitmap getTransparentSignatureBitmap(boolean trimBlankSpace) {
if (!trimBlankSpace) {
return getTransparentSignatureBitmap();
}
ensureSignatureBitmap();
int imgHeight = mSignatureBitmap.getHeight();
int imgWidth = mSignatureBitmap.getWidth();
int backgroundColor = Color.TRANSPARENT;
int xMin = Integer.MAX_VALUE,
xMax = Integer.MIN_VALUE,
yMin = Integer.MAX_VALUE,
yMax = Integer.MIN_VALUE;
boolean foundPixel = false;
// Find xMin
for (int x = 0; x < imgWidth; x++) {
boolean stop = false;
for (int y = 0; y < imgHeight; y++) {
if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
xMin = x;
stop = true;
foundPixel = true;
break;
}
}
if (stop)
break;
}
// Image is empty...
if (!foundPixel)
return null;
// Find yMin
for (int y = 0; y < imgHeight; y++) {
boolean stop = false;
for (int x = xMin; x < imgWidth; x++) {
if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
yMin = y;
stop = true;
break;
}
}
if (stop)
break;
}
// Find xMax
for (int x = imgWidth - 1; x >= xMin; x--) {
boolean stop = false;
for (int y = yMin; y < imgHeight; y++) {
if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
xMax = x;
stop = true;
break;
}
}
if (stop)
break;
}
// Find yMax
for (int y = imgHeight - 1; y >= yMin; y--) {
boolean stop = false;
for (int x = xMin; x <= xMax; x++) {
if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
yMax = y;
stop = true;
break;
}
}
if (stop)
break;
}
return Bitmap.createBitmap(mSignatureBitmap, xMin, yMin, xMax - xMin, yMax - yMin);
}
private boolean isDoubleClick() {
if (mClearOnDoubleClick) {
if (mFirstClick != 0 && System.currentTimeMillis() - mFirstClick > DOUBLE_CLICK_DELAY_MS) {
mCountClick = 0;
}
mCountClick++;
if (mCountClick == 1) {
mFirstClick = System.currentTimeMillis();
} else if (mCountClick == 2) {
long lastClick = System.currentTimeMillis();
if (lastClick - mFirstClick < DOUBLE_CLICK_DELAY_MS) {
this.clear();
return true;
}
}
}
return false;
}
private TimedPoint getNewPoint(float x, float y) {
int mCacheSize = mPointsCache.size();
TimedPoint timedPoint;
if (mCacheSize == 0) {
// Cache is empty, create a new point
timedPoint = new TimedPoint();
} else {
// Get point from cache
timedPoint = mPointsCache.remove(mCacheSize - 1);
}
return timedPoint.set(x, y);
}
private void recyclePoint(TimedPoint point) {
mPointsCache.add(point);
}
private void addPoint(TimedPoint newPoint) {
mPoints.add(newPoint);
int pointsCount = mPoints.size();
if (pointsCount > 3) {
ControlTimedPoints tmp = calculateCurveControlPoints(mPoints.get(0), mPoints.get(1), mPoints.get(2));
TimedPoint c2 = tmp.c2;
recyclePoint(tmp.c1);
tmp = calculateCurveControlPoints(mPoints.get(1), mPoints.get(2), mPoints.get(3));
TimedPoint c3 = tmp.c1;
recyclePoint(tmp.c2);
Bezier curve = mBezierCached.set(mPoints.get(1), c2, c3, mPoints.get(2));
TimedPoint startPoint = curve.startPoint;
TimedPoint endPoint = curve.endPoint;
float velocity = endPoint.velocityFrom(startPoint);
velocity = Float.isNaN(velocity) ? 0.0f : velocity;
velocity = mVelocityFilterWeight * velocity
+ (1 - mVelocityFilterWeight) * mLastVelocity;
// The new width is a function of the velocity. Higher velocities
// correspond to thinner strokes.
float newWidth = strokeWidth(velocity);
// The Bezier's width starts out as last curve's final width, and
// gradually changes to the stroke width just calculated. The new
// width calculation is based on the velocity between the Bezier's
// start and end mPoints.
addBezier(curve, mLastWidth, newWidth);
mLastVelocity = velocity;
mLastWidth = newWidth;
// Remove the first element from the list,
// so that we always have no more than 4 mPoints in mPoints array.
recyclePoint(mPoints.remove(0));
recyclePoint(c2);
recyclePoint(c3);
} else if (pointsCount == 1) {
// To reduce the initial lag make it work with 3 mPoints
// by duplicating the first point
TimedPoint firstPoint = mPoints.get(0);
mPoints.add(getNewPoint(firstPoint.x, firstPoint.y));
}
}
private void addBezier(Bezier curve, float startWidth, float endWidth) {
mSvgBuilder.append(curve, (startWidth + endWidth) / 2);
ensureSignatureBitmap();
float originalWidth = mPaint.getStrokeWidth();
float widthDelta = endWidth - startWidth;
float drawSteps = (float) Math.floor(curve.length());
for (int i = 0; i < drawSteps; i++) {
// Calculate the Bezier (x, y) coordinate for this step.
float t = ((float) i) / drawSteps;
float tt = t * t;
float ttt = tt * t;
float u = 1 - t;
float uu = u * u;
float uuu = uu * u;
float x = uuu * curve.startPoint.x;
x += 3 * uu * t * curve.control1.x;
x += 3 * u * tt * curve.control2.x;
x += ttt * curve.endPoint.x;
float y = uuu * curve.startPoint.y;
y += 3 * uu * t * curve.control1.y;
y += 3 * u * tt * curve.control2.y;
y += ttt * curve.endPoint.y;
// Set the incremental stroke width and draw.
mPaint.setStrokeWidth(startWidth + ttt * widthDelta);
mSignatureBitmapCanvas.drawPoint(x, y, mPaint);
expandDirtyRect(x, y);
}
mPaint.setStrokeWidth(originalWidth);
}
private ControlTimedPoints calculateCurveControlPoints(TimedPoint s1, TimedPoint s2, TimedPoint s3) {
float dx1 = s1.x - s2.x;
float dy1 = s1.y - s2.y;
float dx2 = s2.x - s3.x;
float dy2 = s2.y - s3.y;
float m1X = (s1.x + s2.x) / 2.0f;
float m1Y = (s1.y + s2.y) / 2.0f;
float m2X = (s2.x + s3.x) / 2.0f;
float m2Y = (s2.y + s3.y) / 2.0f;
float l1 = (float) Math.sqrt(dx1 * dx1 + dy1 * dy1);
float l2 = (float) Math.sqrt(dx2 * dx2 + dy2 * dy2);
float dxm = (m1X - m2X);
float dym = (m1Y - m2Y);
float k = l2 / (l1 + l2);
if (Float.isNaN(k)) k = 0.0f;
float cmX = m2X + dxm * k;
float cmY = m2Y + dym * k;
float tx = s2.x - cmX;
float ty = s2.y - cmY;
return mControlTimedPointsCached.set(getNewPoint(m1X + tx, m1Y + ty), getNewPoint(m2X + tx, m2Y + ty));
}
private float strokeWidth(float velocity) {
return Math.max(mMaxWidth / (velocity + 1), mMinWidth);
}
/**
* Called when replaying history to ensure the dirty region includes all
* mPoints.
*
* @param historicalX the previous x coordinate.
* @param historicalY the previous y coordinate.
*/
private void expandDirtyRect(float historicalX, float historicalY) {
if (historicalX < mDirtyRect.left) {
mDirtyRect.left = historicalX;
} else if (historicalX > mDirtyRect.right) {
mDirtyRect.right = historicalX;
}
if (historicalY < mDirtyRect.top) {
mDirtyRect.top = historicalY;
} else if (historicalY > mDirtyRect.bottom) {
mDirtyRect.bottom = historicalY;
}
}
/**
* Resets the dirty region when the motion event occurs.
*
* @param eventX the event x coordinate.
* @param eventY the event y coordinate.
*/
private void resetDirtyRect(float eventX, float eventY) {
// The mLastTouchX and mLastTouchY were set when the ACTION_DOWN motion event occurred.
mDirtyRect.left = Math.min(mLastTouchX, eventX);
mDirtyRect.right = Math.max(mLastTouchX, eventX);
mDirtyRect.top = Math.min(mLastTouchY, eventY);
mDirtyRect.bottom = Math.max(mLastTouchY, eventY);
}
private void setIsEmpty(boolean newValue) {
mIsEmpty = newValue;
if (mOnSignedListener != null) {
if (mIsEmpty) {
mOnSignedListener.onClear();
} else {
mOnSignedListener.onSigned();
}
}
}
private void ensureSignatureBitmap() {
if (mSignatureBitmap == null) {
mSignatureBitmap = Bitmap.createBitmap(getWidth(), getHeight(),
Bitmap.Config.ARGB_8888);
mSignatureBitmapCanvas = new Canvas(mSignatureBitmap);
}
}
private int convertDpToPx(float dp) {
return Math.round(getContext().getResources().getDisplayMetrics().density * dp);
}
// public void setDrawingColor(int color) {
// mCurrentOp.reset();
// mCurrentOp.type = DrawOp.Type.PAINT;
// mCurrentOp.color = color;
// }
//
// public void setDrawingStroke(int stroke) {
// mCurrentOp.reset();
// mCurrentOp.type = DrawOp.Type.PAINT;
// mCurrentOp.stroke = stroke;
// }
//
// public void enableEraser() {
// mCurrentOp.reset();
// mCurrentOp.type = DrawOp.Type.ERASE;
// }
//
// public void clearDrawing() {
// mDrawOps.clear();
// mUndoneOps.clear();
// mCurrentOp.reset();
// invalidate();
// }
//
// public void undoOperation() {
// if (mDrawOps.size() > 0) {
// DrawOp last = mDrawOps.remove(mDrawOps.size() - 1);
// mUndoneOps.add(last);
// invalidate();
// }
// }
//
// public void redoOperation() {
// if (mUndoneOps.size() > 0) {
// DrawOp redo = mUndoneOps.remove(mUndoneOps.size() - 1);
// mDrawOps.add(redo);
// invalidate();
// }
// }
//
// private void drawOp(Canvas canvas, DrawOp op) {
// if (op.path.isEmpty()) {
// return;
// }
// final Paint paint;
// if (op.type == DrawOp.Type.PAINT) {
// paint = mPaint;
// paint.setColor(op.color);
// paint.setStrokeWidth(op.stroke);
// } else {
// paint = mPaint;
// paint.setStrokeWidth(op.stroke);
// }
// mLayerCanvas.drawPath(op.path, paint);
// }
public interface OnSignedListener {
void onStartSigning();
void onSigned();
void onClear();
}
// private static class DrawOp {
// public final Path path = new Path();
// public Type type;
// public int color;
// public int stroke;
//
// public DrawOp() {
// //
// }
//
// public DrawOp(DrawOp op) {
// this.path.set(op.path);
// this.type = op.type;
// this.color = op.color;
// this.stroke = op.stroke;
// }
//
// public void reset() {
// this.path.reset();
// }
//
// public static enum Type {
// PAINT, ERASE;
// }
// }
}
我使用代码进行撤消和重做但是在撤消细线仍显示
之后出现了一些问题答案 0 :(得分:1)
canvas
包含bitmap
,您执行的任何绘制操作都会影响基础位图的像素。所以没有&#34;开箱即用的东西&#34;撤消或重做。
为了实现撤消或重做功能,您需要将绘制操作作为命令。您需要将所有绘制操作存储在列表中。例如,我们假设您执行以下操作:
所有这些操作都必须以某种格式保存在列表中。现在当你
我希望你明白这个想法,你需要封装绘图操作,将它们存储在一个数组中并执行这些操作。
如何执行ERASE?
再次没有擦除操作,擦除只是在画布上再次绘制,这意味着您需要设置颜色以匹配背景颜色。因此,如果您的画布为红色,并且您使用白色绘制,则擦除意味着使用红色绘制。
那么如果背景不是简单的颜色,而是图像呢?在这种情况下,你需要有2个画布。一个是原始画布(带有位图A的画布A)和实际的图像位图,另一个是临时画布(带有位图B的画布B)。所以要擦除,你必须做如下的事情:
canvas.drawBitmap()
)