Android实现签名,并且转为图片和打印出来
Android实现签名的功能
自定义一个签名画布的面板
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class SignatureView extends View {
private Paint paint = new Paint();
private Path path = new Path();
private Paint borderPaint = new Paint();
public SignatureView(Context context, AttributeSet attrs) {
super(context, attrs);
paint.setAntiAlias(true);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeJoin(Paint.Join.ROUND);
paint.setStrokeWidth(5f);
borderPaint.setStyle(Paint.Style.STROKE);
borderPaint.setStrokeWidth(10f);
borderPaint.setColor(Color.RED);
setBackgroundColor(Color.WHITE);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(path, paint);
canvas.drawRect(0, 0, getWidth(), getHeight(), borderPaint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
path.moveTo(x, y);
return true;
case MotionEvent.ACTION_MOVE:
path.lineTo(x, y);
break;
case MotionEvent.ACTION_UP:
// do nothing
break;
default:
return false;
}
invalidate();
return true;
}
public Bitmap getSignatureBitmap() {
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
draw(canvas);
return bitmap;
}
public void clearSignature() {
path.reset();
invalidate();
}
}
写一个签名的页面布局
画布面板定义完成后,直接在layout文件中使用就行了:红色框中就是签名的地方。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.util.SignatureActivity">
<com.pft.mobilemr.customview.SignatureView
android:id="@+id/signature_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/bottom_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/bottom_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/signature_view">
<Button
android:id="@+id/cancel_signature"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginHorizontal="10dp"
android:text="@string/cancel_signature"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/clear_signature"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/clear_signature"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginHorizontal="10dp"
android:text="@string/clear_signature"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/confirm_signature"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@+id/cancel_signature"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/confirm_signature"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginHorizontal="10dp"
android:text="@string/confirm_signature"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@+id/clear_signature"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
activity中签名时需要的一些逻辑和功能实现
activity中直接写逻辑,并且设置三个按钮各自的逻辑,具体按钮方法实现的代码在自定义View页面都有写,直接调用就行,点击确认按钮后会将签名转成bitmap格式的图片,然后转成byte[]格式后放入Intent后传入其他页面。
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.View;
import com.pft.mobilemr.R;
import com.pft.mobilemr.customview.SignatureView;
import java.io.ByteArrayOutputStream;
/**
* 签名页面
*/
public class SignatureActivity extends AppCompatActivity {
private SignatureView signatureView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_signature);
init();
}
private void init() {
signatureView = findViewById(R.id.signature_view);
findViewById(R.id.clear_signature).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
signatureView.clearSignature();
}
});
findViewById(R.id.cancel_signature).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent();
setResult(RESULT_CANCELED, intent);
finish();
}
});
findViewById(R.id.confirm_signature).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Bitmap signatureBitmap = signatureView.getSignatureBitmap();
Intent intent = new Intent();
byte[] signature = Bitmap2Bytes(signatureBitmap);
intent.putExtra("signature", signature);
setResult(RESULT_OK, intent);
finish();
}
});
}
private byte[] Bitmap2Bytes(Bitmap bm) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 100, baos);
return baos.toByteArray();
}
}
整体来说逻辑并不是很混乱,代码的实现也并不是很难。
签名最好使用横屏,所以对这个activity设置打开方式为横屏。
<activity
android:name=".activity.util.SignatureActivity"
android:label="@string/activity_signature"
android:screenOrientation="landscape" />
将签名的图片进行展示并打印
其他页面接收数据并展示出来
签名页面点击确认签名后,会跳转回前一个页面,并且将签名的内容一起传过去。获取到byte[]格式的图片后,转成bitmap后,在imageView控件中放入这个bitmap图片。
if (requestCode == REQUEST_CODE_SIGNATURE && resultCode == RESULT_OK && data != null) {
byteArray = data.getByteArrayExtra("signature");
if (byteArray == null) {
toastMsg("签名数据为空");
return;
}
// 处理签名数据
ImageView imageView = findViewById(R.id.signature_img);
imageBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
imageView.setImageBitmap(imageBitmap);
}
打印签名
打印签名之前,需要对这个签名的bitmap进行修整,使其大小符合我们的签名纸宽度,不然会出现只打印一半的情况。
开始打印:
try {
p.reset();
byte[] bytes = Printer.draw2PxPoint(p.bmpzoomTo58(imageBitmap));
p.write(bytes);
p.enter();
p.feed();
p.feed();
p.close();
} catch (Exception e) {
e.printStackTrace();
}
其中draw2PxPoint()方法就是对签名图片进行修整。
图片修整
/*************************************************************************
* 假设一个240*240的图片,分辨率设为24, 共分10行打印
* 每一行,是一个 240*24 的点阵, 每一列有24个点,存储在3个byte里面。
* 每个byte存储8个像素点信息。因为只有黑白两色,所以对应为1的位是黑色,对应为0的位是白色
**************************************************************************/
/**
* 把一张Bitmap图片转化为打印机可以打印的字节流
*
* @param bmp
* @return
*/
public static byte[] draw2PxPoint(Bitmap bmp) throws IOException {
//用来存储转换后的 bitmap 数据。为什么要再加1000,这是为了应对当图片高度无法
//整除24时的情况。比如bitmap 分辨率为 240 * 250,占用 7500 byte,
//但是实际上要存储11行数据,每一行需要 24 * 240 / 8 =720byte 的空间。再加上一些指令存储的开销,
//所以多申请 1000byte 的空间是稳妥的,不然运行时会抛出数组访问越界的异常。
int size = bmp.getWidth() * bmp.getHeight() / 8 + 7500;
byte[] data = new byte[size];
int k = 0;
//设置行距为0的指令
data[k++] = 0x1B;
data[k++] = 0x33;
data[k++] = 0x01;
// 逐行打印
for (int j = 0; j < bmp.getHeight() / 24f; j++) {
//打印图片的指令
data[k++] = 0x1B;
data[k++] = 0x2A;
data[k++] = 33;
data[k++] = (byte) (bmp.getWidth() % 256); //nL
data[k++] = (byte) (bmp.getWidth() / 256); //nH
//对于每一行,逐列打印
for (int i = 0; i < bmp.getWidth(); i++) {
//每一列24个像素点,分为3个字节存储
for (int m = 0; m < 3; m++) {
//每个字节表示8个像素点,0表示白色,1表示黑色
for (int n = 0; n < 8; n++) {
byte b = px2Byte(i, j * 24 + m * 8 + n, bmp);
data[k] += data[k] + b;
}
k++;
}
}
if (k < data.length) {
data[k++] = 10;//换行
}
}
return data;
}
/**
* 灰度图片黑白化,黑色是1,白色是0
*
* @param x 横坐标
* @param y 纵坐标
* @param bit 位图
* @return
*
*/
public static byte px2Byte(int x, int y, Bitmap bit) {
if (x < bit.getWidth() && y < bit.getHeight()) {
byte b;
int pixel = bit.getPixel(x, y);
int red = (pixel & 0x00ff0000) >> 16; // 取高两位
int green = (pixel & 0x0000ff00) >> 8; // 取中两位
int blue = pixel & 0x000000ff; // 取低两位
int gray = RGB2Gray(red, green, blue);
if (gray < 128) {
b = 1;
} else {
b = 0;
}
return b;
}
return 0;
}
/**
* 图片灰度的转化
*
*/
private static int RGB2Gray(int r, int g, int b) {
int gray = (int) (0.29900 * r + 0.58700 * g + 0.11400 * b); //灰度转化公式
return gray;
}
DecimalFormat df = new DecimalFormat("0.00");
public Bitmap bmpzoomTo58(Bitmap mBitmap) {
if (mBitmap.getWidth() >= 380) {
float c = Float.valueOf(df.format((float) mBitmap.getWidth() / 380));
int newHight = Integer.parseInt(new java.text.DecimalFormat("0").format(mBitmap.getHeight() / c));
mBitmap = zoomImg(mBitmap, 380, newHight);
} else {
float c = Float.valueOf(df.format((float) 380 / mBitmap.getWidth()));
Log.e("", "c:" + c);
int newHight1 = Integer.parseInt(new java.text.DecimalFormat("0").format(c * mBitmap.getHeight()));
mBitmap = zoomImg(mBitmap, 380, newHight1);
}
return mBitmap;
}
// 缩放图片
private static Bitmap zoomImg(Bitmap bm, int newWidth, int newHeight) {
// 获得图片的宽高
int width = bm.getWidth();
int height = bm.getHeight();
// 计算缩放比例
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 取得想要缩放的matrix参数
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
// 得到新的图片
Bitmap newbm = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true);
return newbm;
}
示例图片: