Android(安卓)电子签名/手写签名 保存到相册详解
16lz
2021-01-26
ps:因公司推崇线上信息办公化 设计到客户签名 将客户签好的名字上传到服务器 因此 写了一个demo
废话不多哔哔 上效果图:
这里我运用的是自定义view
//权限 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
public class SignatureView extends View { private static final String TAG = SignatureView.class.getSimpleName(); public static final int PEN_WIDTH = 10; public static final int PEN_COLOR = Color.BLACK; public static final int BACK_COLOR = Color.WHITE; //画笔x坐标起点 private float mPenX; //画笔y坐标起点 private float mPenY; private Paint mPaint = new Paint(); private Path mPath = new Path(); private Canvas mCanvas; private Bitmap cacheBitmap; //画笔宽度 private int mPentWidth = PEN_WIDTH; //画笔颜色 private int mPenColor = PEN_COLOR; //画板颜色 private int mBackColor = BACK_COLOR; private boolean isTouched = false; private String mSavePath = null; public SignatureView(Context context) { this(context, null); } public SignatureView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public SignatureView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.SignatureView);//拿到样式集合 //画笔颜色 mPenColor = typedArray.getColor(R.styleable.SignatureView_penColor, PEN_COLOR); //画板颜色 mBackColor = typedArray.getColor(R.styleable.SignatureView_backColor, BACK_COLOR); //画笔宽度 mPentWidth = typedArray.getInt(R.styleable.SignatureView_penWidth, PEN_WIDTH); typedArray.recycle();//回收资源 init(); } private void init() { mPaint.setAntiAlias(true);//抗锯齿 mPaint.setStyle(Paint.Style.STROKE);//描边 mPaint.setStrokeWidth(mPentWidth);//设置画笔宽度 mPaint.setColor(mPenColor); //设置画笔颜色 black } public boolean getTouched() { return isTouched; } public void setPentWidth(int pentWidth) { mPentWidth = pentWidth; } public void setPenColor(@ColorInt int penColor) { mPenColor = penColor; } public void setBackColor(@ColorInt int backColor) { mBackColor = backColor; } /** * 清空签名 */ public void clear() { if (mCanvas != null) { isTouched = false; mCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);//清除画布内容 mCanvas.drawColor(mBackColor);//重新设置画布颜色 invalidate(); } } /** * 保存图片 * * @param path 保存的地址 * @param clearBlank 是否清除空白区域 * @param blank 空白区域留空距离 * @throws IOException */ public void save(String path, boolean clearBlank, int blank,Context context) throws IOException { if (TextUtils.isEmpty(path)) { return; } mSavePath = path; Bitmap bitmap = cacheBitmap; Log.i("zahuishi","@"+bitmap); saveFile(context,bitmap);// if (clearBlank) {// bitmap = clearBlank(bitmap, blank);// }// ByteArrayOutputStream bos = new ByteArrayOutputStream();// bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);// byte[] buffer = bos.toByteArray();// if (buffer != null) {// File file = new File(path);// if (file.exists()) {// file.delete();// }// OutputStream os = new FileOutputStream(file);// os.write(buffer);// os.close();// bos.close();// } } public static void saveFile(Context context, Bitmap bm) throws IOException { File dirFile = new File(Environment.getExternalStorageDirectory().getPath()); if (!dirFile.exists()) { dirFile.mkdir(); } String fileName = UUID.randomUUID().toString() + ".jpg"; File myCaptureFile = new File(Environment.getExternalStorageDirectory().getPath() + "/DCIM/Camera/" + fileName); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(myCaptureFile)); bm.compress(Bitmap.CompressFormat.JPEG, 80, bos); bos.flush(); bos.close(); //把图片保存后声明这个广播事件通知系统相册有新图片到来 Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); Uri uri = Uri.fromFile(myCaptureFile); intent.setData(uri); context.sendBroadcast(intent); } /** * 获取Bitmap缓存 */ public Bitmap getBitmap() { setDrawingCacheEnabled(true); buildDrawingCache(); Bitmap bitmap = getDrawingCache(); setDrawingCacheEnabled(false); return bitmap; } /** * 获取保存路径 */ public String getSavePath() { return mSavePath; } /** * 逐行扫描,清除边界空白 * * @param blank 边界留多少个像素 */ private Bitmap clearBlank(Bitmap bmp, int blank) { int height = bmp.getHeight(); int width = bmp.getWidth(); int top = 0, left = 0, right = 0, bottom = 0; int[] pixs = new int[width]; boolean isStop; //扫描上边距不等于背景颜色的第一个点 for (int i = 0; i < height; i++) { bmp.getPixels(pixs, 0, width, 0, i, width, 1); isStop = false; for (int pix : pixs) { if (pix != mBackColor) { top = i; isStop = true; break; } } if (isStop) { break; } } //扫描下边距不等于背景颜色的第一个点 for (int i = height - 1; i >= 0; i--) { bmp.getPixels(pixs, 0, width, 0, i, width, 1); isStop = false; for (int pix : pixs) { if (pix != mBackColor) { bottom = i; isStop = true; break; } } if (isStop) { break; } } pixs = new int[height]; //扫描左边距不等于背景颜色的第一个点 for (int x = 0; x < width; x++) { bmp.getPixels(pixs, 0, 1, x, 0, 1, height); isStop = false; for (int pix : pixs) { if (pix != mBackColor) { left = x; isStop = true; break; } } if (isStop) { break; } } //扫描右边距不等于背景颜色的第一个点 for (int x = width - 1; x > 0; x--) { bmp.getPixels(pixs, 0, 1, x, 0, 1, height); isStop = false; for (int pix : pixs) { if (pix != mBackColor) { 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(bmp, left, top, right - left, bottom - top); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) {//布局发生变化 重新回去控件大小 super.onSizeChanged(w, h, oldw, oldh); //创建一个空位图,没有色彩,宽高和bitmap2一样 cacheBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); //用来承载画的内容。 mCanvas = new Canvas(cacheBitmap); //画板颜色 mCanvas.drawColor(mBackColor); isTouched = false; } @Override protected void onDraw(Canvas canvas) {//绘制 super.onDraw(canvas); canvas.drawBitmap(cacheBitmap, 0, 0, mPaint); canvas.drawPath(mPath, mPaint); } @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: mPenX = event.getX(); mPenY = event.getY(); mPath.moveTo(mPenX, mPenY);//不会进行绘制,只用于移动移动画笔。 return true; case MotionEvent.ACTION_MOVE: isTouched = true; float x = event.getX(); float y = event.getY(); float penX = mPenX; float penY = mPenY; float dx = Math.abs(x - penX);//取绝对值 float dy = Math.abs(y - penY); if (dx >= 3 || dy >= 3) { float cx = (x + penX) / 2; float cy = (y + penY) / 2; mPath.quadTo(penX, penY, cx, cy); mPenX = x; mPenY = y; } invalidate(); break; case MotionEvent.ACTION_UP: mCanvas.drawPath(mPath, mPaint); mPath.reset();//清除之前绘制的path break; default: break; } return super.onTouchEvent(event); }}
在MainActivity使用
<?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="com.f1reking.signatureview_sample.MainActivity" > <Button android:id="@+id/btn_clear" android:layout_width="0dp" android:layout_height="wrap_content" android:text="清除" app:layout_constraintHorizontal_weight="1" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/btn_save" tools:ignore="MissingConstraints" /> <Button android:id="@+id/btn_save" android:layout_width="0dp" android:layout_height="wrap_content" android:text="保存" app:layout_constraintHorizontal_weight="1" app:layout_constraintLeft_toRightOf="@+id/btn_clear" app:layout_constraintRight_toRightOf="parent" tools:ignore="MissingConstraints" /> <com.f1reking.signatureview.SignatureView android:id="@+id/view_signature" android:layout_width="match_parent" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toBottomOf="@+id/btn_clear" app:layout_constraintVertical_weight="1" app:backColor="#FFFFFF" /></androidx.constraintlayout.widget.ConstraintLayout>
主要逻辑代码:
public class MainActivity extends AppCompatActivity { private static final String TAG ="didijiuwoa" ; private SignatureView mSignatureView; private Button btnClear; private Button btnSave; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); checkPermission(); initView(); } private void checkPermission() { //Android平台版本,如我的版本为Android 7.1.2 Log.v(TAG,"Build.VERSION.RELEASE----->"+ Build.VERSION.RELEASE); //当前手机版本-API版本号 Log.v(TAG,"android.os.Build.VERSION.SDK_INT----->"+Build.VERSION.SDK_INT); //android 6.0 对应的 API版本号23 Log.v(TAG,"Build.VERSION_CODES.M----->"+Build.VERSION_CODES.M); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {//android 6.0以上 Log.v(TAG,"测试手机版本为:android 6.0以上"); int writePermission = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE); if (writePermission != PackageManager.PERMISSION_GRANTED) { Log.v(TAG,"测试手机版本为:android 6.0以上--->未申请--->申请读写权限"); requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 100); }else{ Log.v(TAG,"测试手机版本为:android 6.0以上--->已申请"); } }else{//android 6.0以下 Log.v(TAG,"测试手机版本为:android 6.0以下"); } } private void initView() { mSignatureView = findViewById(R.id.view_signature); btnClear = findViewById(R.id.btn_clear); btnSave = findViewById(R.id.btn_save); btnClear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mSignatureView.clear(); } }); btnSave.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) {// Intent intent = new Intent(MainActivity.this,ImageActivity.class);// startActivity(intent); if (mSignatureView.getTouched()) { try { mSignatureView.save("/sdcard/sign.png", true, 10,MainActivity.this); Toast.makeText(MainActivity.this, "图片保存在:"+mSignatureView.getSavePath(), Toast.LENGTH_SHORT).show(); } catch (IOException e) { e.printStackTrace(); } } else { Toast.makeText(MainActivity.this, "请先签名", Toast.LENGTH_SHORT).show(); } } }); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 100) { if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE) && grantResults[0] == PackageManager.PERMISSION_GRANTED) {//允许 Log.v(TAG, "测试手机版本为:android 6.0以上--->未申请--->申请读写权限--->成功!"); } else {//拒绝 Log.v(TAG, "测试手机版本为:android 6.0以上--->未申请--->申请读写权限--->失败!"); Toast.makeText(this, "请赋予读写权限,否则应用将无法使用!", Toast.LENGTH_LONG).show(); MainActivity.this.finish(); } } }}
ok 这就结束了
更多相关文章
- 用百度地图API实现Android定位功能(2.6版本为例)
- 5.0 导入错误 出现"Loading data for Android(安卓)5.0"
- Android-Gradle依赖统一管理
- Android(安卓)OpenGL探索之纯色背景绘制
- ANDROID 设置状态栏与标题背景颜色一致
- 新建Android项目出现错误
- android studio3.1.4更新3.2.1版本問題Unknown host 'android-mi
- Android(安卓)如何将Canvas上绘制的内容保存成本地图片
- Android面试时的问题,实现半透明的popupwindow的源码