Android自定义View实现屏幕手写签名、银行、保险行业等电子签名

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/qq_37717853/article/details/86641516

通过自定义view实现屏幕手写签名效果,可以上一步,清空,可以保存签名为图片格式到本地。

效果:

在这里插入图片描述

思路其实很简单,肯定是自定义一个控件,然后在里面动态获取用户的触屏滑动的点的坐标并保存下来,一开始我想到的是保存path,但是又想过用canvas单纯的drawPoint(),或者drawlines()之类的,稍微试了下,不是很好,最终我还是选择了一开始想到的drawPath()的方案。
1.自定义控件customView继承自View,当然,为了使控件更灵活易拓展,将里面用到几个属性也设定为自定义,那么在values文件夹下创建一个attr_customview.xml的文件,在里面定义customView的属性:
<resources>
    <declare-styleable name="customView">
        <!--画布背景色-->
        <attr name="canvasColor" format="color"/>
        <!--画笔颜色-->
        <attr name="paintColor" format="color"/>
        <!--画笔大小-->
        <attr name="paintSrowkeWidth" format="integer"/>
    </declare-styleable>
</resources>
2.在customView的构造方法进行一些初始化,这里除了拿到自定义的属性、以及创建画笔之外,还定义了两个集合,一个用于保存路径 path,一个用于保存路径上的点 point,为什么要用集合呢,因为签名大都不可能一笔完成吧,所以肯定有多条path需要在canvas中呈现。
public customView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //初始化
        init(context,attrs);
    }
/*初始化*/
private void init(Context context,AttributeSet attrs) {
        //获取TypedArray,取出自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.customView);
        //画布背景色,默认为白色
        canvasColor = array.getColor(R.styleable.customView_canvasColor,Color.WHITE);
        //画笔颜色,默认为黑色
        paintColor = array.getColor(R.styleable.customView_paintColor,Color.BLACK);
        //画笔笔触大小,默认为5
        paintStrokeWidth = array.getInteger(R.styleable.customView_paintSrowkeWidth,5);

        pathList = new ArrayList<>();//路径集合
        pointList = new ArrayList<>();//点集合

        paint = new Paint();//画笔
        paint.setColor(paintColor);//画笔颜色
        paint.setStyle(Paint.Style.STROKE);//描边
        paint.setStrokeWidth(paintStrokeWidth);//笔触大小
        paint.setAntiAlias(true);

    }
3.然后是重写onTouchEvent方法,通过触屏事件进行相应操作,这里我试图直接在响应事件的时候保存坐标点到pathList集合中,然后调用postInvalidate()去刷新画布,但是这样操作画布画出来的是空白,不明所以,如果有大神知道具体原因望悉知,这里我的解决方案是通过发handler消息,在onTouchEvent之外进行保存path以及刷新canvas的操作。
public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN://按下时
                //清空点集合
                pointList.clear();
                break;
            case MotionEvent.ACTION_MOVE://移动时
                //动态获取全部坐标点
                Point point = new Point();
                point.x = (int) event.getX();
                point.y = (int) event.getY();
                pointList.add(point);
                break;
            case MotionEvent.ACTION_UP://松开手时
                //画路径
                handler.sendEmptyMessage(1);
                break;
        }
    return true;
}
	 /*收到消息,保存路径*/
    @SuppressLint("HandlerLeak")
    Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //创建一条新的路径
            Path path = new Path();
            //起始点
            path.moveTo(pointList.get(0).x,pointList.get(0).y);
            //画路径
            for(Point point:pointList){
                path.lineTo(point.x,point.y);
            }
            //保存路径到集合
            pathList.add(path);
            //刷新画布
            invalidate();
        }
    };
4.然后是在onDraw()中将所有路径画出来:
protected void onDraw(Canvas canvas) {
        //设置画布底色
        canvas.drawColor(canvasColor);
        //遍历保存的所有路径
        if(pathList!=null && pathList.size()>0){
            for (Path path : pathList) {
                //画路径
                canvas.drawPath(path, paint);
            }
        }
        super.onDraw(canvas);
}
5.接下来是 [保存] [上一步] [清空] 这三种情况的实现,同样是在customView中用几个public方法实现它们,以便activity中直接调用。上一步和清空操作比较简单,对pathList进行操作即可,保存操作这里我用的是getDrawingCache的方式:
清空
public void cleanCanvas(){
        //即清空集合中的所有路径
        pathList.clear();
        //刷新画布
        invalidate();
    }
上一步
public void lastStep(){
        //去除集合中的最后一条路径,即为用户的最后一步操作
        if(pathList!=null && pathList.size()>0){
            pathList.remove(pathList.size()-1);
        }
        //刷新画布
        invalidate();
    }
保存
public void saveCanvas(Context context) {
        long time = System.currentTimeMillis();//系统时间
        @SuppressLint("SimpleDateFormat")
        String picName = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(time));//图片名称
        File file = Environment.getExternalStorageDirectory();//保存在内部存储
        String sava_file = file.getAbsolutePath()+"/"+context.getPackageName();//内部存储根目录作为创建文件夹路径
        String save_path = sava_file+"/" + picName + ".jpg";//图片路径
        //生成文件夹
        createFile(sava_file);
        //生成图片并保存
        try {
            getDrawingCache().compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(new File(save_path)));//保存,quality为图片大小
        } catch (Exception e) {
            e.printStackTrace();
        }
        Toast.makeText(context, "文件已保存到:"+save_path, Toast.LENGTH_LONG).show();
    }
    
/*在内部存储根目录下,生成一个以包名命名的文件夹*/
private void createFile(String filePath) {
    File file;
    try {
        file = new File(filePath);
        if(!file.exists()){//如果文件夹不存在
            file.mkdir();//创建
        }
    }catch (Exception e){
        Log.i("error:",e.toString());
    }
}
6.接下来是activity中的操作,其实就很简单了,初始化控件,设置监听,调用自定义控件方法,就这关键几步,当然如果是Android6.0或者以上的系统,对于文件存储权限的申请要进行动态申请,这里也做了简单的适配。
设置监听:
 private void setOnClickListeners() {
        //保存
        tvSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //保存图片到本地
                savePic();
            }
        });
        //上一步
        tvLast.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                cvCanvas.lastStep();//调用自定义控件中的方法
            }
        });
        //清空
        tvClean.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                cvCanvas.cleanCanvas();//调用自定义控件中的方法
            }
        });
    }
动态申请权限,保存图片:
/*保存图片到本地*/
    private void savePic() {
        //版本适配,如果是Android6.0及以上
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            //先判断是否有权限,不等于1即没有授权
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                //申请权限
                ActivityCompat.requestPermissions(MainActivity.this, PERMISSIONS_STORAGE, 9527);
            }
        } else {
            cvCanvas.setDrawingCacheEnabled(true);//开启缓存
            cvCanvas.saveCanvas(MainActivity.this);//保存画布内容
            cvCanvas.setDrawingCacheEnabled(false);//关闭缓存
        }
    }

    /*要申请的权限*/
    private static String[] PERMISSIONS_STORAGE = {
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
    };

    /*动态申请权限返回监听*/
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 9527) {//返回授权成功
            //保存图片
            cvCanvas.setDrawingCacheEnabled(true);//开启缓存
            cvCanvas.saveCanvas(MainActivity.this);//保存画布内容
            cvCanvas.setDrawingCacheEnabled(false);//关闭缓存
        }
    }
到这里基本就可以收工了,layout文件就是customView的一个引用加上三个按钮,这里就不贴出来啦,对了,别忘了在AndroidManifest.xml中添加权限:
    <!--添加写入数据权限-->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
最后照例提供demo:

下载地址:https://download.csdn.net/download/qq_37717853/10937680

猜你喜欢

转载自blog.csdn.net/qq_37717853/article/details/86641516