Android之签字板
文章目录
- 前言
- 一、效果图
- 二、实现步骤
- 1.GestureSignatureView类
- 2.xml布局
- 3.Activity类(kotlin)
- 4.Activity类(Java)
- 5.动态申请权限(kotlin)
- 6.动态申请权限(Java)
- 总结
前言
随着公司发展需求,很多金融APP都会涉及到需要用户签字的环节,所以在此贴出代码以供参考少踩坑。
一、效果图

二、实现步骤
1.GestureSignatureView类
package com.example.kotlinbasedome.activity.utils;import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;/*** @Author : CaoLiulang* @Time : 2023/7/7 17:02* @Description :自定义签字板*/
public class GestureSignatureView extends View {private static final String TAG = "GestureSignatureView";private Path mPath;//绘制路径private Paint mPaint;// 绘制画笔private Canvas mCanvas;//背景画布private Bitmap mMBitmap;//背景bitmapprivate boolean isTouchedSignature = false;//是否签名 默认为falsepublic GestureSignatureView(Context context) {this(context, null);}public GestureSignatureView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public GestureSignatureView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initPaint();}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);Log.d(TAG, "onMeasure: 测量的宽高:" + getMeasuredWidth() + "-----------" + getMeasuredHeight());}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);mMBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);mCanvas = new Canvas(mMBitmap);mCanvas.drawColor(Color.TRANSPARENT);mCanvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));}private void initPaint() {mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);mPaint.setColor(Color.BLACK);mPaint.setStrokeWidth(10.0f);mPaint.setStrokeCap(Paint.Cap.ROUND);mPaint.setStyle(Paint.Style.STROKE);mPaint.setStrokeJoin(Paint.Join.ROUND);mPaint.setDither(true);mPath = new Path();}@Overrideprotected void onDraw(Canvas canvas) {canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));canvas.drawBitmap(mMBitmap, 0, 0, mPaint);// 通过画布绘制多点形成的图形canvas.drawPath(mPath, mPaint);}private float[] downPoint = new float[2];private float[] previousPoint = new float[2];/*** 监听触摸事件的回调** @param event* @return*/@Overridepublic boolean onTouchEvent(MotionEvent event) {//获取距离自身(点击位置)左边界的距离downPoint[0] = event.getX();//获取距离自身(点击位置)上边界的距离downPoint[1] = event.getY();switch (event.getAction()) {//手势开始case MotionEvent.ACTION_DOWN:previousPoint[0] = downPoint[0];previousPoint[1] = downPoint[1];// moveTo 不会进行绘制,只用于移动移动画笔。mPath.moveTo(downPoint[0], downPoint[1]);break;//手势过程case MotionEvent.ACTION_MOVE:float dX = Math.abs(downPoint[0] - previousPoint[0]);float dY = Math.abs(downPoint[1] - previousPoint[1]);// 两点之间的距离大于等于3时,生成贝塞尔绘制曲线if (dX >= 3 || dY >= 3) {// 设置贝塞尔曲线的操作点为起点和终点的一半float cX = (downPoint[0] + previousPoint[0]) / 2;float cY = (downPoint[1] + previousPoint[1]) / 2;//quadTo 用于绘制圆滑曲线,即贝塞尔曲线 二次贝塞尔,实现平滑曲线;previousX, previousY为操作点,cX, cY为终点mPath.quadTo(previousPoint[0], previousPoint[1], cX, cY);// 第二次执行时,第一次结束调用的坐标值将作为第二次调用的初始坐标值previousPoint[0] = downPoint[0];previousPoint[1] = downPoint[1];}break;//手势结束case MotionEvent.ACTION_UP://设置签名成功状态isTouchedSignature = true;mCanvas.drawPath(mPath, mPaint);mPath.reset();break;}invalidate();return true;}// 缩放public static Bitmap resizeImage(Bitmap bitmap, int width, int height) {int originWidth = bitmap.getWidth();int originHeight = bitmap.getHeight();float scaleWidth = ((float) width) / originWidth;float scaleHeight = ((float) height) / originHeight;Matrix matrix = new Matrix();matrix.postScale(scaleWidth, scaleHeight);Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, originWidth,originHeight, matrix, true);return resizedBitmap;}public Bitmap getPaintBitmap() {return resizeImage(mMBitmap, 320, 480);}public void clear() {if (mCanvas != null) {isTouchedSignature = false;mPath.reset();mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);invalidate();}}/*** 保存画板** @param path 保存到路径*/public void save(String path) {try {save(path, true, 50);} catch (Exception e) {e.printStackTrace();}}public Bitmap getBitmap() {return mMBitmap;}/*** 保存画板** @param path 保存到路径* @param clearBlank 是否清除空白区域* @param blank 边缘空白区域*/public void save(String path, boolean clearBlank, int blank) throws IOException {Bitmap bitmap = mMBitmap;if (clearBlank) {bitmap = clearBlank(mMBitmap, blank);}Bitmap littleBmp = ConstantsUtil.smallImage(bitmap, 700);Bitmap newBitmap = Bitmap.createBitmap(littleBmp.getWidth(), littleBmp.getHeight(), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(newBitmap);canvas.drawColor(Color.WHITE);canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));canvas.drawBitmap(littleBmp, 0, 0, null);ByteArrayOutputStream bos = new ByteArrayOutputStream();newBitmap.compress(Bitmap.CompressFormat.JPEG, 50, bos);byte[] buffer = bos.toByteArray();if (buffer != null) {File file = new File(path);OutputStream outputStream = new FileOutputStream(file);outputStream.write(buffer);outputStream.close();scanMediaFile(file);}}/*** 是否有签名** @return*/public boolean getTouched() {return isTouchedSignature;}/*** 逐行扫描 清除边界空白。** @param bp* @param blank 边距留多少个像素* @return*/private Bitmap clearBlank(Bitmap bp, int blank) {int HEIGHT = bp.getHeight();int WIDTH = bp.getWidth();int top = 0, left = 0, right = 0, bottom = 0;int[] pixs = new int[WIDTH];boolean isStop;for (int y = 0; y < HEIGHT; y++) {bp.getPixels(pixs, 0, WIDTH, 0, y, WIDTH, 1);isStop = false;for (int pix : pixs) {if (pix != Color.TRANSPARENT) {top = y;isStop = true;break;}}if (isStop) {break;}}for (int y = HEIGHT - 1; y >= 0; y--) {bp.getPixels(pixs, 0, WIDTH, 0, y, WIDTH, 1);isStop = false;for (int pix : pixs) {if (pix != Color.TRANSPARENT) {bottom = y;isStop = true;break;}}if (isStop) {break;}}pixs = new int[HEIGHT];for (int x = 0; x < WIDTH; x++) {bp.getPixels(pixs, 0, 1, x, 0, 1, HEIGHT);isStop = false;for (int pix : pixs) {if (pix != Color.TRANSPARENT) {left = x;isStop = true;break;}}if (isStop) {break;}}for (int x = WIDTH - 1; x > 0; x--) {bp.getPixels(pixs, 0, 1, x, 0, 1, HEIGHT);isStop = false;for (int pix : pixs) {if (pix != Color.TRANSPARENT) {right = x;isStop = true;break;}}if (isStop) {break;}}if (blank < 0) {blank = 0;}left = left - blank > 0 ? left - blank : 0;top = top - blank > 0 ? top - blank : 0;right = right + blank > WIDTH - 1 ? WIDTH - 1 : right + blank;bottom = bottom + blank > HEIGHT - 1 ? HEIGHT - 1 : bottom + blank;return Bitmap.createBitmap(bp, left, top, right - left, bottom - top);}private void scanMediaFile(File photo) {Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);Uri contentUri = Uri.fromFile(photo);mediaScanIntent.setData(contentUri);getContext().sendBroadcast(mediaScanIntent);}
}
2.xml布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:background="#ffffff"android:orientation="vertical"><ImageViewandroid:layout_width="match_parent"android:layout_height="1dp"android:layout_marginTop="28dp"android:src="#DEDFE2" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="2"><com.example.kotlinbasedome.activity.utils.GestureSignatureViewandroid:id="@+id/signSave_gsv_signature"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@android:color/white" /></LinearLayout><ImageViewandroid:layout_width="match_parent"android:layout_height="1dp"android:src="#DEDFE2" /><LinearLayoutandroid:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:orientation="horizontal"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_marginLeft="20dp"android:text="Preview:"android:textSize="20sp"android:typeface="serif"></TextView><ImageViewandroid:id="@+id/singImg"android:layout_width="wrap_content"android:layout_height="40dp"android:layout_gravity="center"android:layout_marginLeft="20dp"></ImageView></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:padding="10dp"><TextViewandroid:id="@+id/signSave_tv_cancel"android:layout_width="0dp"android:layout_height="35dp"android:layout_weight="1"android:background="#CDCDCD"android:clickable="true"android:gravity="center"android:onClick="onClick"android:text="EXIT"android:textColor="@color/white"android:textSize="16sp"android:typeface="serif" /><TextViewandroid:id="@+id/signSave_tv_clear"android:layout_width="0dp"android:layout_height="35dp"android:layout_marginLeft="10dp"android:layout_weight="1"android:background="#acacac"android:clickable="true"android:gravity="center"android:onClick="onClick"android:text="CLEAN"android:textColor="@color/white"android:textSize="16sp"android:typeface="serif" /><TextViewandroid:id="@+id/signSave_tv_save"android:layout_width="0dp"android:layout_height="35dp"android:layout_marginLeft="10dp"android:layout_weight="1"android:background="#1d9eec"android:clickable="true"android:gravity="center"android:onClick="onClick"android:text="CONFIRM"android:textColor="@color/white"android:textSize="16sp"android:typeface="serif" /></LinearLayout></LinearLayout>
3.Activity类(kotlin)
/*** @Author : CaoLiulang* @Time : 2023/7/7 17:02* @Description :签名*/
class Signature : Activity(), OnClickListener {private lateinit var signSave_gsv_signature: GestureSignatureViewprivate lateinit var singImg: ImageViewprivate lateinit var signSave_tv_save: TextViewprivate lateinit var signSave_tv_clear: TextViewprivate lateinit var signSave_tv_cancel: TextViewprivate lateinit var message: Stringprivate var imagurl: String = ""override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)//去掉状态栏if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {val decorView = window.decorViewval option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLEdecorView.systemUiVisibility = optionwindow.statusBarColor = Color.parseColor("#00000000")}//修改状态栏文字为黑色window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN orView.SYSTEM_UI_FLAG_LIGHT_STATUS_BARsetContentView(R.layout.signature)instantiation()}fun instantiation() {signSave_gsv_signature = findViewById(R.id.signSave_gsv_signature)singImg = findViewById(R.id.singImg)signSave_tv_save = findViewById(R.id.signSave_tv_save)signSave_tv_clear = findViewById(R.id.signSave_tv_clear)signSave_tv_cancel = findViewById(R.id.signSave_tv_cancel)signSave_tv_save.setOnClickListener(this)signSave_tv_clear.setOnClickListener(this)signSave_tv_cancel.setOnClickListener(this)}@RequiresApi(Build.VERSION_CODES.O)override fun onClick(v: View?) {when (v?.id) {//保存R.id.signSave_tv_save ->//防止多次触发if (ButtonUtils.isFastDoubleClick(R.id.signSave_tv_save) === false) {if (imagurl == "") {if (!signSave_gsv_signature.touched) {ToastUtilsKT.showToast1("Please sign first")return}val file = File(ConstantsUtil.IMG_FOLDER_PATH)file.mkdirs()val fillPath: String =ConstantsUtil.IMG_FOLDER_PATH + "signImg" + System.currentTimeMillis() + ".jpg"Log.i("w--", fillPath)signSave_gsv_signature.save(fillPath)println("图片路径打印:$fillPath")Glide.with(this).load(fillPath).into(singImg)imagurl = fillPath//网络请求} else {//网络请求}}//清空R.id.signSave_tv_clear -> {signSave_gsv_signature.clear()singImg.setImageDrawable(null)imagurl = ""}//返回R.id.signSave_tv_cancel ->finish()}}
4.Activity类(Java)
public class Signature extends Activity implements View.OnClickListener {private TextView signSave_tv_cancel;//退出private TextView signSave_tv_clear;//清除private TextView signSave_tv_save;//保存private GestureSignatureView signSave_gsv_signature;//签字板private ImageView singImg;//签名图片展示private String message;//返回消息private String imagurl = "";//图片路径@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);//去掉状态栏if (Build.VERSION.SDK_INT >= 21) {View decorView = getWindow().getDecorView();int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;decorView.setSystemUiVisibility(option);getWindow().setStatusBarColor(Color.parseColor("#00000000"));}//修改状态栏文字为黑色getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);setContentView(R.layout.signature);instantiation();}//实例化private void instantiation() {signSave_tv_cancel = findViewById(R.id.signSave_tv_cancel);signSave_tv_clear = findViewById(R.id.signSave_tv_clear);signSave_tv_save = findViewById(R.id.signSave_tv_save);signSave_gsv_signature = findViewById(R.id.signSave_gsv_signature);singImg = findViewById(R.id.singImg);signSave_tv_save.setOnClickListener(this);signSave_tv_clear.setOnClickListener(this);signSave_tv_cancel.setOnClickListener(this);}@SuppressLint("NewApi")@Overridepublic void onClick(View view) {switch (view.getId()) {//退出case R.id.signSave_tv_cancel:finish();break;//清除case R.id.signSave_tv_clear:signSave_gsv_signature.clear();singImg.setImageDrawable(null);imagurl = "";break;//保存case R.id.signSave_tv_save://防止多次触发if (ButtonUtils.isFastDoubleClick(R.id.signSave_tv_save) == false) {if (imagurl.equals("")) {if (!signSave_gsv_signature.getTouched()) {ToastUtils.ToastCllShow("您尚未签字");return;}File file = new File(ConstantsUtil.IMG_FOLDER_PATH);file.mkdirs();String fillPath = ConstantsUtil.IMG_FOLDER_PATH + "signImg" + System.currentTimeMillis() + ".jpg";Log.i("w--", fillPath);signSave_gsv_signature.save(fillPath);System.out.println("图片路径打印:" + fillPath);Glide.with(this).load(fillPath).into(singImg);imagurl = fillPath;//网络请求} else {//网络请求}}break;}}
5.动态申请权限(kotlin)
//6.0才用动态权限if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//读写权限if (ContextCompat.checkSelfPermission(this@PDFWebViewActivity,Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this@PDFWebViewActivity,arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),1)} else {//有权限startActivity(Intent(this@PDFWebViewActivity,Signature::class.java))}}
6.动态申请权限(Java)
//6.0才用动态权限if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//读写权限if (ContextCompat.checkSelfPermission(PDFWebViewActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(PDFWebViewActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);} else {startActivity(new Intent(PDFWebViewActivity.this, Signature.class));}}
总结
以上便是签字板所有代码了,kotlin和Java我都分别贴了上去,注册activity的时候需要设置横屏显示即可,竖屏也行,看需求,然后保持图片需要用到权限这个一定要记得,欢迎讨论指正!
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
