Android自定義View實現屏幕手寫簽名、銀行、保險行業等電子簽名

通過自定義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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章