【Android】手写签名(87/100)
自定义View:
package top.lc951.myandroid.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import java.util.ArrayList;
import java.util.List;
import top.lc951.myandroid.R;
/**
* 自定义签名View
*
* @author lichong
* 2022年08月03日14:22:22
*/
public class SignatureView extends View {
public static final String TAG = SignatureView.class.getSimpleName();
private Paint mPathPaint = new Paint(); // 声明一个画笔对象
private Path mPath = new Path(); // 声明一个路径对象
private int mPathPaintColor = Color.BLACK; // 画笔颜色
private int mStrokeWidth = 3; // 画笔线宽
private PathPosition mPathPos = new PathPosition(); // 路径位置
private List<PathPosition> mPathList = new ArrayList<>(); // 路径位置列表
private PointF mLastPos; // 上次触摸点的横纵坐标
// 定义一个路径位置实体类,包括上个落点的横纵坐标,以及下个落点的横纵坐标
public class PathPosition {
public PointF prePos; // 上个落点的横纵坐标
public PointF nextPos; // 下个落点的横纵坐标
}
public SignatureView(Context context) {
this(context, null);
}
public SignatureView(Context context, AttributeSet attrs) {
super(context, attrs);
if (attrs != null) {
// 根据SignatureView的属性定义,从布局文件中获取属性数组描述
TypedArray attrArray = getContext().obtainStyledAttributes(attrs, R.styleable.SignatureView);
// 根据属性描述定义,获取布局文件中的画笔颜色
mPathPaintColor = attrArray.getColor(R.styleable.SignatureView_paint_color, Color.BLACK);
// 根据属性描述定义,获取布局文件中的画笔线宽
mStrokeWidth = attrArray.getInt(R.styleable.SignatureView_stroke_width, 3);
attrArray.recycle(); // 回收属性数组描述
}
initView(); // 初始化视图
}
// 初始化视图
private void initView() {
mPathPaint.setStrokeWidth(mStrokeWidth); // 设置画笔的线宽
mPathPaint.setStyle(Paint.Style.STROKE); // 设置画笔的类型。STROK表示空心,FILL表示实心
mPathPaint.setColor(mPathPaintColor); // 设置画笔的颜色
setDrawingCacheEnabled(true); // 开启当前视图的绘图缓存
}
// 清空画布
public void clear() {
mPath.reset(); // 重置路径对象
mPathList.clear(); // 清空路径列表
postInvalidate(); // 立即刷新视图(线程安全方式)
}
// 撤销上一次绘制
public void revoke() {
if (mPathList.size() > 0) {
// 移除路径位置列表中的最后一个路径
mPathList.remove(mPathList.size() - 1);
mPath.reset(); // 重置路径对象
for (int i = 0; i < mPathList.size(); i++) {
PathPosition pp = mPathList.get(i);
// 移动到上一个坐标点
mPath.moveTo(pp.prePos.x, pp.prePos.y);
// 连接上一个坐标点和下一个坐标点
mPath.quadTo(pp.prePos.x, pp.prePos.y, pp.nextPos.x, pp.nextPos.y);
}
postInvalidate(); // 立即刷新视图(线程安全方式)
}
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawPath(mPath, mPathPaint); // 在画布上绘制指定路径线条
}
// 在发生触摸事件时触发
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: // 按下手指
mPath.moveTo(event.getX(), event.getY()); // 移动到指定坐标点
mPathPos.prePos = new PointF(event.getX(), event.getY());
break;
case MotionEvent.ACTION_MOVE: // 移动手指
// 连接上一个坐标点和当前坐标点
mPath.quadTo(mLastPos.x, mLastPos.y, event.getX(), event.getY());
mPathPos.nextPos = new PointF(event.getX(), event.getY());
mPathList.add(mPathPos); // 往路径位置列表添加路径位置
mPathPos = new PathPosition(); // 创建新的路径位置
mPathPos.prePos = new PointF(event.getX(), event.getY());
break;
case MotionEvent.ACTION_UP: // 松开手指
// 连接上一个坐标点和当前坐标点
mPath.quadTo(mLastPos.x, mLastPos.y, event.getX(), event.getY());
break;
}
mLastPos = new PointF(event.getX(), event.getY());
postInvalidate(); // 立即刷新视图(线程安全方式)
return true;
}
}
自定义属性:
<declare-styleable name="SignatureView">
<attr name="paint_color" format="color" />
<attr name="stroke_width" format="integer" />
</declare-styleable>
使用:
if (v.getId() == R.id.btn_save_signature) { // 点击了保存签名按钮
if (TextUtils.isEmpty(mImagePath)) {
Toast.makeText(SignatureActivity.this, "请先开始然后结束签名", Toast.LENGTH_LONG).show();
return;
}
BitmapUtil.notifyPhotoAlbum(SignatureActivity.this, mImagePath); // 通知相册来了张新图片
Toast.makeText(SignatureActivity.this, "已保存签名图片,请到系统相册查看", Toast.LENGTH_LONG).show();
} else if (v.getId() == R.id.btn_begin_signature) { // 点击了开始签名按钮
// 开启签名视图的绘图缓存
signatureView.setDrawingCacheEnabled(true);
} else if (v.getId() == R.id.btn_reset_signature) { // 点击了重置按钮
signatureView.clear(); // 清空签名视图
} else if (v.getId() == R.id.btn_revoke_signature) { // 点击了回退按钮
signatureView.revoke(); // 回退签名视图的最近一笔绘画
} else if (v.getId() == R.id.btn_end_signature) { // 点击了结束签名按钮
if (!signatureView.isDrawingCacheEnabled()) { // 签名视图的绘图缓存不可用
Toast.makeText(SignatureActivity.this, "请先开始签名", Toast.LENGTH_LONG).show();
} else { // 签名视图的绘图缓存当前可用
Bitmap bitmap = signatureView.getDrawingCache(); // 从绘图缓存获取位图对象
// 生成图片文件的保存路径
// mImagePath = String.format("%s/%s.jpg",
// getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).toString(),
// DateUtil.getNowDateTime());
// BitmapUtil.saveImage(mImagePath, bitmap); // 把位图保存为图片文件
// signatureIvNew.setImageURI(Uri.parse(mImagePath)); // 设置图像视图的路径对象
// 延迟100毫秒后启动绘图缓存的重置任务
new Handler(Looper.myLooper()).postDelayed(() -> {
// 关闭签名视图的绘图缓存
signatureView.setDrawingCacheEnabled(false);
// 开启签名视图的绘图缓存
signatureView.setDrawingCacheEnabled(true);
}, 100);
}
}
}
自研产品推荐
历时一年半多开发终于smartApi-v1.0.0版本在2023-09-15晚十点正式上线
smartApi是一款对标国外的postman的api调试开发工具,由于开发人力就作者一个所以人力有限,因此v1.0.0版本功能进行精简,大功能项有:
- api参数填写
- api请求响应数据展示
- PDF形式的分享文档
- Mock本地化解决方案
- api列表数据本地化处理
- 再加上UI方面的打磨
为了更好服务大家把之前的公众号和软件激活结合,如有疑问请大家反馈到公众号即可,下个版本30%以上的更新会来自公众号的反馈。
嗯!先解释不上服务端原因,API调试工具的绝大多数时候就是一个数据模型、数据处理、数据模型理解共识的问题解决工具,所以作者结合自己十多年开发使用的一些痛点来打造的,再加上服务端开发一般是面向企业的,作者目前没有精力和时间去打造企业服务。再加上没有资金投入所以服务端开发会滞后,至于什么时候会进行开发,这个要看募资情况和用户反馈综合考虑。虽然目前国内有些比较知名的api工具了,但作者使用后还是觉得和实际使用场景不符。如果有相关吐槽也可以在作者的公众号里反馈蛤!
下面是一段smartApi使用介绍: