您的位置:首页 > 移动开发 > Android开发

安卓-电子签名signature

2018-01-02 15:23 423 查看
1.先上效果图:



2.首先需要绘制自定义的view,用于电子签名:
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;

import com.xiaoying.mysignature.R;

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

/**
* 绘制SignaturePad的View 用于签名
*/
public class SignaturePad extends View {
//视图状态
private List<TimedPoint> mPoints;
private boolean mIsEmpty;
private float mLastTouchX;
private float mLastTouchY;
private float mLastVelocity;
private float mLastWidth;
private RectF mDirtyRect;
private final SvgBuilder mSvgBuilder = new SvgBuilder();

//隐藏状态
private List<TimedPoint> mPointsCache = new ArrayList<>();
private ControlTimedPoints mControlTimedPointsCached = new ControlTimedPoints();
private Bezier mBezierCached = new Bezier();

//可以配置的参数
private int mMinWidth;
private int mMaxWidth;
private float mVelocityFilterWeight;
private OnSignedListener mOnSignedListener;
private boolean mClearOnDoubleClick;

//单次点击值
private long mFirstClick;
private int mCountClick;
private static final int DOUBLE_CLICK_DELAY_MS = 200;

//默认的属性值
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;

private Paint mPaint = new Paint();
private Bitmap mSignatureBitmap = null;
private Canvas mSignatureBitmapCanvas = null;

public SignaturePad(Context context, AttributeSet attrs) {
super(context, attrs);

TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.SignaturePad,
0, 0);

//可以配置的参数
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();
}

//固定的配置参数
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);

//更新矩形内的部分视图
mDirtyRect = new RectF();
clear();
}

/**
* 从资源中设置电子笔显示的颜色,默认为黑色
*
* @param colorRes
*/
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);
}

/**
* 设置像素中笔画的最小宽度
*
* @param minWidth 单位 dp
*/
public void setMinWidth(float minWidth) {
mMinWidth = convertDpToPx(minWidth);
}

/**
* 设置像素中笔画的最大宽度
*
* @param maxWidth 单位 dp
*/
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();

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
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:
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;

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);
}
}

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
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();

//生
4000
成可替换的变量
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();
} else {
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
//移除布局侦听器
ViewTreeObserverCompat.removeOnGlobalLayoutListener(getViewTreeObserver(), this);
//电子签名图
setSignatureBitmap(signature);
}
});
}
}

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;

//x轴最小值
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;
}

//电子图片为空
if (!foundPixel)
return null;

//y轴最小值
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;
}

//x轴最大值
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;
}

//y轴最大值
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) {
//缓存为空时创建一个对象
timedPoint = new TimedPoint();
} else {
//从缓存中获取
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;

//对应的比划宽度
float newWidth = strokeWidth(velocity);
//开始和结束的绘制点
addBezier(curve, mLastWidth, newWidth);

mLastVelocity = velocity;
mLastWidth = newWidth;

//从列表中删除第一个元素,总长度不超过4个绘制点
recyclePoint(mPoints.remove(0));

recyclePoint(c2);
recyclePoint(c3);
} else if (pointsCount == 1) {
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++) {
//计算这个步骤的 Bezier(x,y) 坐标
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;

//设置增量笔画宽度和绘制
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);
}

/**
* 记录历史画布上绘制的区域
*
* @param historicalX 上次x轴点
* @param historicalY 上次y轴点
*/
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;
}
}

/**
* 当动作事件发生时重置已绘制的区域
*
* @param eventX 当前x轴点
* @param eventY 当前y轴点
*/
private void resetDirtyRect(float eventX, float eventY) {
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 interface OnSignedListener {
void onStartSigning();

void onSigned();

void onClear();
}
}

3.在/src/res/values目录下添加attr.xml<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SignaturePad">
<attr name="penMinWidth" format="dimension" />
<attr name="penMaxWidth" format="dimension" />
<attr name="penColor" format="color" />
<attr name="velocityFilterWeight" format="float" />
<attr name="clearOnDoubleClick" format="boolean" />
</declare-styleable>
</resources>
4.添加xml文件<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<com.xiaoying.mysignature.signature.SignaturePad
android:id="@+id/signaturePad"
android:layout_width="match_parent"
android:layout_height="500dp" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="510dp"
android:orientation="horizontal">

<TextView
android:id="@+id/btnClear"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@null"
android:enabled="false"
android:gravity="center"
android:paddingBottom="11dp"
android:paddingTop="11dp"
android:text="清除签名"
android:textColor="@color/colorPrimary"
android:textSize="16sp" />

<TextView
android:id="@+id/btnSave"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@null"
android:enabled="false"
android:gravity="center"
android:paddingBottom="11dp"
android:paddingTop="11dp"
android:text="保存签名"
android:textColor="@color/colorPrimary"
android:textSize="16sp" />
</LinearLayout>
</FrameLayout>

5.获取主要的java文件import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

import com.xiaoying.mysignature.signature.SignaturePad;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class MainActivity extends AppCompatActivity implements SignaturePad.OnSignedListener {

@BindView(R.id.signaturePad)
SignaturePad signaturePad;
@BindView(R.id.btnClear)
TextView btnClear;
@BindView(R.id.btnSave)
TextView btnSave;

private File photo;
private Bitmap signatureBitmap;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
signaturePad.setOnSignedListener(this);
}

@OnClick({R.id.btnClear, R.id.btnSave})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.btnClear:
//清除电子签名
signaturePad.clear();
break;
case R.id.btnSave:
signatureBitmap = signaturePad.getSignatureBitmap();
if (addJpgSignatureToGallery(signatureBitmap)) {
Toast.makeText(this, "保存签名成功", Toast.LENGTH_LONG).show();
//跳转至系统相册
skipPictures();
}
break;
}
}

private Intent skipPictures() {
/* 开启Pictures画面Type设定为image */
Intent intent = new Intent();
intent.setType("image/*");
/* 使用Intent.ACTION_GET_CONTENT这个Action */
intent.setAction(Intent.ACTION_GET_CONTENT);
/* 取得相片后返回本画面 */
startActivityForResult(intent, 1);
//(在onActivityResult方法里,返回的意图里获取图片uri,在通过uri,结合内容提供者在查出图片的路径)
return intent;
}

@Override
public void onStartSigning() {
}

@Override
public void onSigned() {
btnClear.setEnabled(true);
btnSave.setEnabled(true);
}

@Override
public void onClear() {
btnClear.setEnabled(false);
btnSave.setEnabled(false);
}

//将.jpg 签名添加到 Gallery 中
private boolean addJpgSignatureToGallery(Bitmap signature) {
boolean result = false;
try {
photo = new File(getAlbumStorageDir("SignaturePad"), String.format("Signature_%d.jpg", System.currentTimeMillis()));
saveBitmapToJPG(signature, photo);
scanMediaFile(photo);
result = true;
} catch (IOException e) {
e.printStackTrace();
}
return result;
}

private File getAlbumStorageDir(String albumName) {
//电子签名图片存储目录
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e("SignaturePad", "Directory not created")
a7f3
;
}
return file;
}

//将位图保存格式为.jpg
private void saveBitmapToJPG(Bitmap bitmap, File photo) throws IOException {
Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newBitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(bitmap, 0, 0, null);
OutputStream stream = new FileOutputStream(photo);
newBitmap.compress(Bitmap.CompressFormat.JPEG, 80, stream);
stream.close();
}

//媒体文件扫描
private void scanMediaFile(File photo) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.fromFile(photo);
mediaScanIntent.setData(contentUri);
this.sendBroadcast(mediaScanIntent);
}
}

6.附上源码 欢迎留言讨论
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息