Android 实例:手写签名

本文介绍了一种基于Android平台的SignatureView签名板组件实现方法,包括画笔工具类的定义、XML布局配置、Activity中使用示例及权限设置。该组件提供画笔绘制、路径跟踪、清除画布、保存签名等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

根据(https://github.com/Geek8ug/SignatureView ) 整理

1.画笔工具类 SignatureView:

public class SignatureView extends View {
    private Context mContext;
    private int targetWidth = 100, targetHeight = 100;
    private ISignatureCallBack signatureCallBack;

    public void setSignatureCallBack(ISignatureCallBack signatureCallBack) {
        this.signatureCallBack = signatureCallBack;
    }

    /**
     * 笔画X坐标起点
     */
    private float mX;
    /**
     * 笔画Y坐标起点
     */
    private float mY;
    /**
     * 手写画笔
     */
    private final Paint mGesturePaint = new Paint();
    /**
     * 路径
     */
    private final Path mPath = new Path();
    /**
     * 签名画笔
     */
    private Canvas cacheCanvas;
    /**
     * 签名画布
     */
    private Bitmap cachebBitmap;
    /**
     * 画笔宽度 px;
     */
    private int mPaintWidth = 10;
    /**
     * 前景色
     */
    private int mPenColor = Color.BLACK;
    /**
     * 背景色(指最终签名结果文件的背景颜色,默认为透明色)
     */
    private int mBackColor = Color.WHITE;

    /**
     * 是否签字
     */
    private boolean isDown = false;


    public SignatureView(Context context) {
        super(context);
        this.mContext = context;
        init(context);
    }

    public SignatureView(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.mContext = context;
        init(context);
    }

    public SignatureView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        this.mContext = context;
        init(context);
    }

    public void init(Context context) {
        this.mContext = context;
        mGesturePaint.setAntiAlias(true);//抗锯齿
        mGesturePaint.setStyle(Paint.Style.STROKE);
        mGesturePaint.setStrokeWidth(mPaintWidth);
        mGesturePaint.setColor(mPenColor);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                touchDown(event);
                break;
            case MotionEvent.ACTION_MOVE:
                touchMove(event);
                break;
            case MotionEvent.ACTION_UP:
                Log.e("TAG", "onTouchEventup");
                isDown = true;
                break;
        }
        // 更新绘制
        invalidate();
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawPath(mPath, mGesturePaint);
    }

    private long startTime;

    private void touchDown(MotionEvent event) {
        startTime = SystemClock.elapsedRealtime();
        float x = event.getX();
        float y = event.getY();
        mX = x;
        mY = y;
        // mPath绘制的绘制起点
        mPath.moveTo(x, y);
    }

    // 手指在屏幕上滑动时调用
    private void touchMove(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final float previousX = mX;
        final float previousY = mY;
        final float dx = Math.abs(x - previousX);
        final float dy = Math.abs(y - previousY);
        // 两点之间的距离大于等于3时,生成贝塞尔绘制曲线
        if (dx >= 3 || dy >= 3) {
            // 设置贝塞尔曲线的操作点为起点和终点的一半
            float cX = (x + previousX) / 2;
            float cY = (y + previousY) / 2;
            // 二次贝塞尔,实现平滑曲线;previousX, previousY为操作点,cX, cY为终点
            mPath.quadTo(previousX, previousY, cX, cY);
            // 第二次执行时,第一次结束调用的坐标值将作为第二次调用的初始坐标值
            mX = x;
            mY = y;
        }
    }

    /**
     * 清除画板
     */
    public void clear() {
        if (cacheCanvas != null) {
            //更新画板信息
            mGesturePaint.setColor(mPenColor);
            cacheCanvas.drawColor(mBackColor, PorterDuff.Mode.CLEAR);
            mGesturePaint.setColor(mPenColor);
            invalidate();
        }
    }


    /**
     * 保存画板
     *
     * @param path 保存到路径
     */
    public void save(String path) throws IOException {
        save(path, false, 0);
    }

    public void setMakeImage() {
        if (isDown) {
            cachebBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
            cacheCanvas = new Canvas(cachebBitmap);
            cacheCanvas.drawColor(mBackColor);
            cacheCanvas.drawPath(mPath, mGesturePaint);
            if (signatureCallBack != null) {
                Log.e("TAG", "finish");
                Bitmap b = clearBlank(cachebBitmap, 0);
                Log.e("TAG", "finish b-->" + b);
                if (b == null) {
                    clear();
                }
                signatureCallBack.onSignCompeleted(SignatureView.this, clearBlank(cachebBitmap, 0));
            }
            mPath.reset();
            invalidate();
        }
    }

    /**
     * 保存画板
     *
     * @param path       保存到路径
     * @param clearBlank 是否清除边缘空白区域
     * @param blank      要保留的边缘空白距离
     */
    public void save(String path, boolean clearBlank, int blank) throws IOException {

        Bitmap bitmap = cachebBitmap;
        //BitmapUtil.createScaledBitmapByHeight(srcBitmap, 300);//  压缩图片
        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 outputStream = new FileOutputStream(file);
            outputStream.write(buffer);
            outputStream.close();
        }
    }

    /**
     * 获取画板的bitmap
     *
     * @return
     */
    public Bitmap getBitMap() {
        setDrawingCacheEnabled(true);
        buildDrawingCache();
        Bitmap bitmap = getDrawingCache();
        setDrawingCacheEnabled(false);
        return bitmap;
    }

    /**
     * 逐行扫描 清楚边界空白。
     *
     * @param bp
     * @param blank 边距留多少个像素
     * @return
     */
    private Bitmap clearBlank(Bitmap bp, int blank) {
        try {
            int HEIGHT = bp.getHeight();
            int WIDTH = bp.getWidth();
            int top = 0, left = 0, right = 0, bottom = 0;
            int[] pixs = new int[WIDTH];
            boolean isStop;
            for (int y = 0; y < HEIGHT; y++) {
                bp.getPixels(pixs, 0, WIDTH, 0, y, WIDTH, 1);
                isStop = false;
                for (int pix : pixs) {
                    if (pix != mBackColor) {
                        top = y;
                        isStop = true;
                        break;
                    }
                }
                if (isStop) {
                    break;
                }
            }
            for (int y = HEIGHT - 1; y >= 0; y--) {
                bp.getPixels(pixs, 0, WIDTH, 0, y, WIDTH, 1);
                isStop = false;
                for (int pix : pixs) {
                    if (pix != mBackColor) {
                        bottom = y;
                        isStop = true;
                        break;
                    }
                }
                if (isStop) {
                    break;
                }
            }
            pixs = new int[HEIGHT];
            for (int x = 0; x < WIDTH; x++) {
                bp.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--) {
                bp.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;
            Bitmap b = Bitmap.createBitmap(bp, left, top, (right - left), (bottom - top));
            int resultW = b.getWidth() * targetHeight / b.getHeight();
            return Bitmap.createScaledBitmap(b, resultW, targetHeight, false);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public interface ISignatureCallBack {
        void onSignCompeleted(View view, Bitmap bitmap);
    }
}

2.xml文件中使用:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    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=".MainActivity">

    <!--引入画板-->
    <com.example.administrator.xingtest.SignatureView
        android:id="@+id/view_signature"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginBottom="60dp"
        app:penColor="#000"
        app:penWidth="10"
        app:backColor="#fff" />

    <View
        android:layout_alignBottom="@id/view_signature"
        android:layout_width="match_parent"
        android:layout_height="0.5dp"
        android:background="#000"/>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal"
        android:layout_alignParentBottom="true">

        <Button
            android:id="@+id/btn_clear"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="清除"
            android:layout_marginLeft="30dp"
            />

        <Button
            android:id="@+id/btn_save"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:text="保存"
            android:layout_marginRight="30dp"
            />
    </RelativeLayout>


</RelativeLayout>

在这里插入图片描述

3.Activity中使用:

public class MainActivity extends AppCompatActivity {

    private Button btnClear;
    private Button btnSave;
    private SignatureView viewSignature;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initVIew();
    }

    private void initVIew(){
        btnClear = (Button) findViewById(R.id.btn_clear);
        btnSave = (Button) findViewById(R.id.btn_save);
        viewSignature = (SignatureView) findViewById(R.id.view_signature);

        btnClear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //清空画笔
                viewSignature.clear();
            }
        });

        btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {    
                if (viewSignature.getTouched()) {
                    try {
                        viewSignature.save("/sdcard/sign.png", true, 10);
                        Toast.makeText(MainActivity.this, "图片保存在:"+viewSignature.getSavePath(), Toast.LENGTH_SHORT).show();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                } else {
                    Toast.makeText(MainActivity.this, "请先签名", Toast.LENGTH_SHORT).show();
                }
            }
        });
    }
}

在这里插入图片描述

4.attrs.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SignatureView">
        <attr format="color" name="penColor"/>
        <attr format="color" name="backColor"/>
        <attr format="integer" name="penWidth"/>
    </declare-styleable>
</resources>

5.AndroidManifest.xml 添加写权限

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值