Android 电子签名/手写签名 保存到相册详解
阅读原文时间:2021年04月20日阅读:1

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 这就结束了