Android 简易签名板

这篇博客介绍了如何创建一个简单的Android签名板应用,包括SignatureView控件的使用,路径保存与绘制,以及如何将签名保存到相册。作者强调了关键代码的注释,并分享了在图片保存成功后利用广播通知相册更新的实现。此外,还提到了一个优化签名平滑效果的高级实现,推荐学习张明云大神的相关工作。

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

一个简单的练习,手写签名后,可以清空,保存,然后再相册进行查看
!简易签名板
在这里插入图片描述
有5个知识点,需要注意:

在SignatureView的onTouchEvent()方法中,利用mPath.quadTo()方法,使绘制路径变得圆滑
在SignatureView的save()方法中,将View中的内容保存到一个Bitmap中
在SignatureView的closeStream()方法中,所有的读写流都实现了Closeable接口,可以用来关闭流
在SignatureView的clear()方法中,利用PorterDuff.Mode.CLEAR将Canvas中绘制的内容清空
在MainActivity中,图片保存到本地后,需要向系统发送一个广播,通知相册更新
  1. SignatureView 控件

先通过mCanvas利用mPaint将绘制的路径保存进了mBitmap中,再将mBitmap在canvas绘制出来

public class SignatureView extends View {
private Paint mPaint;
private Path mPath;
private Canvas mCanvas;
private Bitmap mBitmap;
private float mLastX, mLastY;//上次的坐标

public SignatureView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}

/***
 * 初始化
 */
private void init() {
    //关闭硬件加速
    setLayerType(View.LAYER_TYPE_SOFTWARE, null);

   //画笔
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
    mPaint.setStrokeWidth(5f);
    mPaint.setColor(Color.parseColor("#FF4081"));
    mPaint.setStyle(Paint.Style.STROKE);        
    mPaint.setStrokeJoin(Paint.Join.ROUND);//使画笔更加圆润
    mPaint.setStrokeCap(Paint.Cap.ROUND);//同上
   
    //路径
    mPath = new Path();

    //保存签名的画布
    post(new Runnable() {//拿到控件的宽和高
        @Override
        public void run() {
            mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
            mCanvas = new Canvas(mBitmap);
        }
    });
}

/**
 * 绘制
 */
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (mBitmap != null) {
        canvas.drawColor(Color.WHITE);//绘制背景白色
        drawSignaturePath(); //将路径绘制在mBitmap上
        canvas.drawBitmap(mBitmap, 0, 0, null);//将mBitmap绘制在canvas上
    }
}

/**
 * 清空绘制内容
 */
public void clear() {
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
    mCanvas.drawPaint(mPaint);
    mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
    mPath.reset();
    invalidate();
}

/**
* 保存到指定的文件夹中
*/
public boolean save(String filePath) {
if (mBitmap != null && mLastY != 0f) {//没有绘制,就不保存
//从View中得到Bitmap
setDrawingCacheEnabled(true);
buildDrawingCache(true);
Bitmap bitmap = getDrawingCache(true);
//保存图片
File file = new File(filePath);
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file);
if (bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)) {
fileOutputStream.flush();
return true;
}
} catch (java.io.IOException e) {
e.printStackTrace();
} finally {
closeStream(fileOutputStream);
}
}
return false;
}

/**
 * 关闭流
 */
private void closeStream(Closeable closeable) {
    if (closeable != null) {
        try {
            closeable.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 绘制签名
 */
private void drawSignaturePath() {
    mCanvas.drawPath(mPath, mPaint);
}

/**
 * 触摸事件 触摸绘制
 */
@Override
public boolean onTouchEvent(MotionEvent event) {
    int action = event.getAction();
    float x = event.getX();
    float y = event.getY();
    switch (action) {
        case MotionEvent.ACTION_DOWN:
            mLastX = x;
            mLastY = y;
            mPath.moveTo(mLastX, mLastY);
            break;
        case MotionEvent.ACTION_MOVE:
            float dx = Math.abs(x - mLastX);
            float dy = Math.abs(y - mLastY);
            if (dx >= 3 || dy >= 3) {//绘制的最小距离 3px
                //利用二阶贝塞尔曲线,使绘制路径更加圆滑
                mPath.quadTo(mLastX, mLastY, (mLastX + x) / 2, (mLastY + y) / 2);
            }
            mLastX = x;
            mLastY = y;
            break;
        case MotionEvent.ACTION_UP:
            mPath.reset();
            break;
    }
    invalidate();
    return true;
}

/**
 * 测量
 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);

    if (wSpecMode == MeasureSpec.EXACTLY && hSpecMode == MeasureSpec.EXACTLY) {
        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
    } else if (wSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(200, hSpecSize);
    } else if (hSpecMode == MeasureSpec.AT_MOST) {
        setMeasuredDimension(wSpecSize, 200);
    }
}

}

关键的地方都有注释
2.Activity 代码

在图片保存成功后,将图片的路径信息利用用广播发送给系统,通知相册更新

public class MainActivity extends AppCompatActivity {
private Button bt_clear,bt_save;
private SignatureView sv;
private final String APP_DIR = “com.signature.view”;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
}

/**
 * 初始化
 */
private void init() {
    final String dir = Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator+APP_DIR+File.separator;
    final File fileDir = new File(dir);
    if (!fileDir.exists()){
        fileDir.mkdirs();
    }

    bt_clear = (Button) findViewById(R.id.bt_clear_main_activity);
    sv = (SignatureView) findViewById(R.id.sv_main_activity);
    bt_clear.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            sv.clear();
        }
    });

    bt_save = (Button) findViewById(R.id.bt_save_main_activity);
    bt_save.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            save(fileDir);
        }
    });
}

/**
 *  保存图片
 */
private void save(File fileDir) {
    final String filePath = getFilePath(fileDir);
    if (sv.save(filePath)){//保存成功
        //发送广播 通知系统相册更新
        Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        Uri uri = Uri.fromFile(new File(filePath));
        intent.setData(uri);
        sendBroadcast(intent);
        Toast.makeText(MainActivity.this, "保存成功!!!", Toast.LENGTH_SHORT).show();
    }
}

/**
 *   得到图片的路径 以及图片的名字
 */
private String getFilePath(File fileDir) {
    SimpleDateFormat simpleDateFormat = (SimpleDateFormat) DateFormat.getDateTimeInstance();
    final String fileName = simpleDateFormat.format(new Date())+".png";
    Log.e("filename","---"+fileName);
    File file = new File(fileDir,fileName);
    return file.getAbsolutePath();
}

}

DateFormat.getDateTimeInstance()这个方法很方便的拿到手机设置的时间显示格式

布局文件:

<?xml version="1.0" encoding="utf-8"?>

<com.szlk.signatureview.view.SignatureView
    android:id="@+id/sv_main_activity"
    android:layout_width="match_parent"
    android:layout_height="400dp" />

<Button
    android:id="@+id/bt_clear_main_activity"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@+id/sv_main_activity"
    android:layout_marginStart="50dp"
    android:text="清空"/>

<Button
    android:id="@+id/bt_save_main_activity"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@+id/sv_main_activity"
    android:layout_alignParentEnd="true"
    android:layout_marginEnd="50dp"
    android:text="保存" />

布局很简陋
3. 最后

主要是练习一下,复习最近一段学习到的一些知识

有一个非常牛的签名板,可以学习一下张明云大神的Android手写优化-更为平滑的签名效果实现

共勉 : )在这里插入图片描述

android电子签名,屏幕上手写签名 搜集很多资料,项目能够完美运行,拿来即可使用,整理备用 应用场景: 就是在屏幕是用手写字,然后保存成图片,简称就是电子签名,可以用在手机上签合同,等技术。 使用技术: 使用了接口回调,绘制完成之后给用户去操作 自定义Dialog,在dialog上画图,给dialog设置主题,dialog的宽高设置为手机屏幕的宽高充满全屏 注意在计算高度的时候记得减去通知栏的高度 注意把画布的背景设置为白色,不然点击缩略图查看的时候是全黑色 参考如下资料: http://hbxflihua.iteye.com/blog/1512765 http://www.jianshu.com/p/c4f017603413 https://github.com/gcacace/android-signaturepad http://download.youkuaiyun.com/download/mmlinux/7687091 1,android 如何让自定义dialog的宽度跟屏幕的宽度一样? 在你dialog.show();后面加上 WindowManager windowManager = getWindowManager(); Display display = windowManager.getDefaultDisplay(); WindowManager.LayoutParams lp = dialog.getWindow().getAttributes(); lp.width = (int)(display.getWidth()); //设置宽度 dialog.getWindow().setAttributes(lp); 2,如何获取通知栏的高度? public int getStatusBarHeight() { int result = 0; int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android"); if (resourceId > 0) { result = getResources().getDimensionPixelSize(resourceId); } return result; } 3,如何对图片进行压缩? http://blog.sina.com.cn/s/blog_497f718e0100sl13.html http://www.cnblogs.com/Soprano/articles/2577152.html
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值