最近项目中有个需要签名的地方,要用到手写签名,开始只是简单的实现手写签名,如图:
后来领导说,能不能实现像毛笔那样签名的效果,那好吧,领导说怎样就怎样吧,而且我也觉得这里用毛笔效果会更好些。那就只好运用贝塞尔曲线的原理了。实现如下:
/**
* This view implements the drawing canvas.
*
* It handles all of the input events and drawing functions.
*/
class PaintView extends View {
private Paint paint;
private Canvas cacheCanvas;
private Bitmap cachebBitmap;
private Path path;
private List<TimePoint> mPoints = new ArrayList<>();
private float mVelocityFilterWeight;
private float mLastTouchX;
private float mLastTouchY;
private float mLastVelocity;
private float mLastWidth;
private int mMinWidth;
private int mMaxWidth;
private RectF mDirtyRect;
public Bitmap getCachebBitmap() {
return cachebBitmap;
}
public void setSignatureBitmap(Bitmap signature) {
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.START);
Canvas canvas = new Canvas(cachebBitmap);
canvas.drawBitmap(signature, drawMatrix, null);
// setIsEmpty(false);
invalidate();
}
public PaintView(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_minWidth, convertDpToPx(3));
mMaxWidth = a.getDimensionPixelSize(
R.styleable.SignaturePad_maxWidth, convertDpToPx(12));
mVelocityFilterWeight = a.getFloat(
R.styleable.SignaturePad_velocityFilterWeight, 0.6f);
} finally {
a.recycle();
}
init();
}
private void init() {
paint = new Paint();
paint.setAntiAlias(true);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLACK);
path = new Path();
cachebBitmap = Bitmap.createBitmap(p.width, (int) (p.height),// *
// 0.8),
Config.ARGB_8888);
cacheCanvas = new Canvas(cachebBitmap);
cacheCanvas.drawColor(Color.WHITE);
mDirtyRect = new RectF();
mLastVelocity = 0;
mLastWidth = (mMinWidth + mMaxWidth) / 2;
path.reset();
invalidate();
}
/**
* Set the velocity filter weight.
*
* @param velocityFilterWeight
* the weight.
*/
public void setVelocityFilterWeight(float velocityFilterWeight) {
mVelocityFilterWeight = velocityFilterWeight;
}
private void ensureSignatureBitmap() {
if (cachebBitmap == null) {
cachebBitmap = Bitmap.createBitmap(p.width, (int) (p.height),// *
// 0.8),
Config.ARGB_8888);
cacheCanvas = new Canvas(cachebBitmap);
}
}
public void clear() {
mPoints.clear();
mLastVelocity = 0;
mLastWidth = (mMinWidth + mMaxWidth) / 2;
path.reset();
if (cacheCanvas != null) {
paint.setColor(BACKGROUND_COLOR);
cacheCanvas.drawPaint(paint);
paint.setColor(Color.BLACK);
cacheCanvas.drawColor(Color.WHITE);
invalidate();
}
}
@Override
protected void onDraw(Canvas canvas) {
// canvas.drawColor(BRUSH_COLOR);
if (cachebBitmap != null) {
if (topBitmap == null) {
topBitmap = Bitmap.createBitmap(
top_layout.getMeasuredWidth(),
top_layout.getMeasuredHeight(), Config.ARGB_8888);
top_layout.draw(new Canvas(topBitmap));
setSignatureBitmap(topBitmap);
}
canvas.drawBitmap(cachebBitmap, 0, 0, paint);
// canvas.drawPath(path, paint);
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
int curW = cachebBitmap != null ? cachebBitmap.getWidth() : 0;
int curH = cachebBitmap != null ? cachebBitmap.getHeight() : 0;
if (curW >= w && curH >= h) {
return;
}
if (curW < w)
curW = w;
if (curH < h)
curH = h;
Bitmap newBitmap = Bitmap.createBitmap(curW, curH,
Bitmap.Config.ARGB_8888);
Canvas newCanvas = new Canvas();
newCanvas.setBitmap(newBitmap);
if (cachebBitmap != null) {
newCanvas.drawBitmap(cachebBitmap, 0, top_layout.getHeight(),
null);
}
cachebBitmap = newBitmap;
cacheCanvas = newCanvas;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled())
return false;
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
getParent().requestDisallowInterceptTouchEvent(true);
mPoints.clear();
mLastTouchX = x;
mLastTouchY = y;
path.moveTo(x, y);
addPoint(new TimePoint(x, y));
}
case MotionEvent.ACTION_MOVE: {
// path.quadTo(cur_x, cur_y, x, y);
resetDirtyRect(x, y);
addPoint(new TimePoint(x, y));
break;
}
case MotionEvent.ACTION_UP: {
// cacheCanvas.drawPath(path, paint);
// path.reset();
// cur_x = x;
// cur_y = y;
resetDirtyRect(x, y);
addPoint(new TimePoint(x, y));
getParent().requestDisallowInterceptTouchEvent(true);
break;
}
default:
return false;
}
// invalidate();
invalidate((int) (mDirtyRect.left - mMaxWidth),
(int) (mDirtyRect.top - mMaxWidth),
(int) (mDirtyRect.right + mMaxWidth),
(int) (mDirtyRect.bottom + mMaxWidth));
return true;
}
public void addPoint(TimePoint point) {
mPoints.add(point);
if (mPoints.size() > 2) {
if (mPoints.size() == 3)
mPoints.add(0, mPoints.get(0));
ControlTimedPoints tmp = calculateCurveControlPoints(
mPoints.get(0), mPoints.get(1), mPoints.get(2));
TimePoint c2 = tmp.c2;
tmp = calculateCurveControlPoints(mPoints.get(1),
mPoints.get(2), mPoints.get(3));
TimePoint c3 = tmp.c1;
Bezier curve = new Bezier(mPoints.get(1), c2, c3,
mPoints.get(2));
TimePoint startPoint = curve.startPoint;
TimePoint endPoint = curve.endPoint;
float velocity = endPoint.getSpeed(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.
mPoints.remove(0);
}
}
/**
* 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);
}
/**
*
* @param curve
* @param startWidth
* @param endWidth
*/
private void addBezier(Bezier curve, float startWidth, float endWidth) {
ensureSignatureBitmap();
float originalWidth = paint.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;
// 设置画笔的大小
paint.setStrokeWidth(startWidth + ttt * widthDelta);
//控制线显示的范围
if (y > top_layout.getHeight()) {
cacheCanvas.drawPoint(x, y, paint);
}
expandDirtyRect(x, y);
}
paint.setStrokeWidth(originalWidth);
}
private ControlTimedPoints calculateCurveControlPoints(TimePoint s1,
TimePoint s2, TimePoint 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;
TimePoint m1 = new TimePoint((s1.x + s2.x) / 2.0f,
(s1.y + s2.y) / 2.0f);
TimePoint m2 = new TimePoint((s2.x + s3.x) / 2.0f,
(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 = (m1.x - m2.x);
float dym = (m1.y - m2.y);
float k = l2 / (l1 + l2);
TimePoint cm = new TimePoint(m2.x + dxm * k, m2.y + dym * k);
float tx = s2.x - cm.x;
float ty = s2.y - cm.y;
return new ControlTimedPoints(new TimePoint(m1.x + tx, m1.y + ty),
new TimePoint(m2.x + tx, m2.y + ty));
}
private float strokeWidth(float velocity) {
return Math.max(mMaxWidth / (velocity + 1), mMinWidth);
}
private int convertDpToPx(float dp) {
return Math
.round(dp
* (getResources().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
}
public Bitmap convertViewToBitmap(View view) {
view.measure(
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
view.buildDrawingCache();
Bitmap bitmap = view.getDrawingCache();
return bitmap;
}
/**
* 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;
}
}
}
最后呈上效果图:
时间: 2024-10-16 03:05:01