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;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* description: 签字板
* author: huangYouGui
* email: 1404186117@qq.com
* createDate: 2023/12/13 9:26
* version: v1.0
*/
public class GestureSignatureView extends View {
private static final String TAG = "GestureSignatureView";
private Path mPath;//绘制路径
private Paint mPaint;// 绘制画笔
private Canvas mCanvas;//背景画布
private Bitmap mMBitmap;//背景bitmap
private boolean isTouchedSignature = false;//是否签名 默认为false
public 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();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.d(TAG, "onMeasure: 测量的宽高:" + getMeasuredWidth() + "-----------" + getMeasuredHeight());
}
@Override
protected 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.WHITE);
mPaint.setStrokeWidth(3.0f);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setDither(true);
mPath = new Path();
}
@Override
protected 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
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.e(TAG, "onTouchEvent: " + event.getAction());
//获取距离自身(点击位置)左边界的距离
downPoint[0] = event.getX();
//获取距离自身(点击位置)上边界的距离
downPoint[1] = event.getY();
// nestedScrollListener.onNestedScroll(false);
switch (event.getAction()) {
//手势开始
case MotionEvent.ACTION_DOWN:
//按下时,禁止父View拦截事件
getParent().requestDisallowInterceptTouchEvent(true);
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:
// 手指抬起时,允许父View拦截事件
getParent().requestDisallowInterceptTouchEvent(false);
// nestedScrollListener.onNestedScroll(true);
//设置签名成功状态
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, 500, 115);
}
public void clear() {
if (mCanvas != null) {
isTouchedSignature = false;
mPath.reset();
mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
invalidate();
}
}
/**
* 保存画板
*
* @param file 保存到路径
*/
public void save(File file) {
try {
save(file, true, 50);
} catch (Exception e) {
e.printStackTrace();
}
}
public Bitmap getBitmap() {
return mMBitmap;
}
public File createFile(File baseFolder) {
if (!baseFolder.exists()) {
baseFolder.mkdirs();
}
String timeStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()).format(new Date());
return new File(baseFolder, "img" + timeStamp + ".jpeg");
}
/**
* 保存画板
*
* @param file 保存到路径
* @param clearBlank 是否清除空白区域
* @param blank 边缘空白区域
*/
public void save(File file, 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(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newBitmap);
canvas.drawColor(Color.parseColor("#151622"));
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG));
canvas.drawBitmap(bitmap, 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);
}
}
自定义签字板
©著作权归作者所有,转载或内容合作请联系作者
- 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
- 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
- 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
推荐阅读更多精彩内容
- 安装: npm install vue-esign --save 在main.js 中引入: import vue...
- 我的简书:https://www.jianshu.com/u/c91e642c4d90我的CSDN:http://...
- 在开发的过程中,有个需求,是添加一个字体选择框,这个简单,我可以直接用了QFontComboBox解决,但是我的需...
- 本篇文章是记录下自己学习的内容 , 代码比较low,请各位大神多多指教 效果为 主要实现方法: 实现下划线。重写d...
- 1.最简单直接的方式设置字体 (1)-初始化控件,拿到该控件的对象,设置字体 这种方式只适用于单个,少数字体需要改...