好习惯先来个效果图

没啥好说的直接上代码
所有的方法都在类内注释的很清楚
直接将他放到layout文件里就行
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Stack;
/**
* 手写签名 主要功能
* 主要功能:绘制,撤销、清除、颜色、背景、宽度设置,导出文件,导入文件
* 驰郡
* wechat:chijun9
* 2023/11/27
*/
public class SignatureView extends View {
private Path path; // 用于绘制路径
private Paint paint; // 用于绘制画笔
private ArrayList<Path> paths = new ArrayList<>(); // 保存所有路径
private ArrayList<Paint> paints = new ArrayList<>(); // 保存所有画笔
private int currentColor = Color.BLACK; // 当前画笔颜色
private int currentWidth = 5; // 当前画笔宽度
private Stack<Bitmap> undoStack = new Stack<>(); // 保存撤销的位图
private Stack<Bitmap> redoStack = new Stack<>(); // 保存重做的位图
public SignatureView(Context context, AttributeSet attrs) {
super(context, attrs);
path = new Path();
paint = new Paint();
//设置画笔是否启用抗锯齿效果 用于减少图形边缘的锯齿状失真,使得图形边缘更加平滑
paint.setAntiAlias(true);
//设置画笔抖动效果 可以在颜色深度较低的设备上模拟更多的颜色
paint.setDither(true);
paint.setColor(currentColor);
//设置画笔绘制模式 FILL(填充)、STROKE(描边)和FILL_AND_STROKE(填充和描边)。
paint.setStyle(Paint.Style.STROKE);
//设置画笔线段连接处样式 MITER(结合处为锐角)、ROUND(结合处为圆弧)和 BEVEL(结合处为直线)
paint.setStrokeJoin(Paint.Join.ROUND);
//画笔的线帽指的是线条的两端的样式,可以是BUTT(无线帽)、ROUND(圆线帽)或SQUARE(方线帽)。
paint.setStrokeCap(Paint.Cap.ROUND);
//设置画笔绘制线条的宽度 以像素为单位的,默认值是 0
paint.setStrokeWidth(currentWidth);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < paths.size(); i++) {
canvas.drawPath(paths.get(i), paints.get(i));
}
}
boolean isUp = true;
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 当手指按下时,创建一个新的路径并将其移动到触摸点,将当前路径和画笔添加到列表中,并创建新的路径和画笔
// path.reset(); // 重置路径
path = new Path(); // 创建新的路径
paint = new Paint(); // 创建新的画笔
//设置画笔是否启用抗锯齿效果 用于减少图形边缘的锯齿状失真,使得图形边缘更加平滑
paint.setAntiAlias(true);
//设置画笔抖动效果 可以在颜色深度较低的设备上模拟更多的颜色
paint.setDither(true);
paint.setColor(currentColor);
//设置画笔绘制模式 FILL(填充)、STROKE(描边)和FILL_AND_STROKE(填充和描边)。
paint.setStyle(Paint.Style.STROKE);
//设置画笔线段连接处样式 MITER(结合处为锐角)、ROUND(结合处为圆弧)和 BEVEL(结合处为直线)
paint.setStrokeJoin(Paint.Join.ROUND);
//画笔的线帽指的是线条的两端的样式,可以是BUTT(无线帽)、ROUND(圆线帽)或SQUARE(方线帽)。
paint.setStrokeCap(Paint.Cap.ROUND);
//设置画笔绘制线条的宽度 以像素为单位的,默认值是 0
paint.setStrokeWidth(currentWidth);
path.moveTo(x, y); // 将路径移动到触摸点
if (isUp) {
paths.add(path); // 将当前路径添加到路径列表中
paints.add(paint); // 将当前画笔添加到画笔列表中
isUp = false;
}
break;
case MotionEvent.ACTION_MOVE:
// 当手指移动时,将路径连接到当前触摸点
path.lineTo(x, y); // 将路径连接到当前触摸点,实现绘制路径
break;
case MotionEvent.ACTION_UP:
isUp = true; //设置当前这段绘制完毕
break;
default:
return false;
}
invalidate(); // 请求重绘视图
return true;
}
/**
* 清除签名
*/
public void clear() {
paths.clear();
paints.clear();
undoStack.clear();
redoStack.clear();
path.reset();
paint.reset();
invalidate();
}
/**
* 撤销上一步操作
*/
public void undo() {
if (paths.size() > 0) {
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
draw(canvas);
undoStack.push(bitmap);
paths.remove(paths.size() - 1);
paints.remove(paints.size() - 1);
invalidate();
}
}
/**
* 重做上一步撤销的操作
*/
public void redo() {
if (redoStack.size() > 0) {
Bitmap bitmap = redoStack.pop();
setSignatureBitmap(bitmap);
}
}
/**
* 设置画笔颜色
* @param color 画笔颜色
*/
public void setColor(int color) {
currentColor = color;
}
/**
* 验证线宽是否有效
* @param width 线宽值
* @return 如果线宽有效则返回true,否则返回false
*/
private boolean isValidWidth(float width) {
return width > 0;
}
/**
* 设置画笔宽度
* @param width 画笔宽度
*/
public void setWidth(float width) {
if (!isValidWidth(width)) {
// 可以在这里添加错误处理逻辑,例如打印错误日志或抛出异常等
return;
}
currentWidth = (int) width;
}
/**
* 改变签名的背景颜色
* @param color 颜色
*/
public void setSignatureBackgroundColor(int color) {
setBackgroundColor(color);
invalidate(); // 更新视图以显示新的背景颜色设置效果
}
/**
* 获取签名位图
* @return 签名位图
*/
public Bitmap getSignatureBitmap() {
Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
draw(canvas);
return bitmap;
}
/**
* 设置签名位图
* @param bitmap 签名位图
*/
public void setSignatureBitmap(Bitmap bitmap) {
clear();
Paint newPaint = new Paint();
newPaint.setAntiAlias(true);
newPaint.setDither(true);
newPaint.setColor(currentColor);
newPaint.setStyle(Paint.Style.STROKE);
newPaint.setStrokeJoin(Paint.Join.ROUND);
newPaint.setStrokeCap(Paint.Cap.ROUND);
newPaint.setStrokeWidth(currentWidth);
paths.add(new Path());
paints.add(newPaint);
paths.get(0).addPath(path);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
draw(canvas);
undoStack.push(bitmap);
invalidate();
}
/**
* 保存签名位图到文件
* @param filename 文件名
* @return 是否保存成功
*/
public boolean saveSignatureToFile(String filename) {
Bitmap bitmap = getSignatureBitmap();
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, new FileOutputStream(filename));
return true;
} catch (FileNotFoundException e) {
e.printStackTrace();
return false;
}
}
/**
* 从文件加载签名位图
* @param filename 文件名
* @return 是否加载成功
*/
public boolean loadSignatureFromFile(String filename) {
Bitmap bitmap = BitmapFactory.decodeFile(filename);
if (bitmap != null) {
setSignatureBitmap(bitmap);
return true;
} else {
return false;
}
}
}
















