文 | Promise Sun
一、手绘签名
最近,项目有个需求是用户在APP上签合同时,需要手绘签名。简单写了一个demo,之后产品又通知改需求了,不用手绘实现的方式了,demo写了却用不上……分享给有需要的朋友吧!
二、功能效果图
手绘签名/清除.jpg 手绘.jpg 签名.jpg三、实现手绘签名
1.首先自定义一个 SignatureView
(注:挺简单的,不作具体分析了,大家直接看代码和相应注释吧。)
import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
/**
* 手绘签名View:实现一个自定义view,可以绘制出轨迹
*/
public class SignatureView extends View implements View.OnTouchListener{
private Bitmap bitmap=null;//用户保存签名的Bitmap
private Path path;
private Rect boundary;
private Canvas myCanvas;//用户保存签名的Canvas
private boolean isdraw;
private int bound,stroke;
private int width,height;
//动态设置边框和画笔粗细,方便调用自定义view
public float getBound() {
return bound;
}
public void setBound(int bound) {
this.bound = bound;
}
public void setStroke(int stroke) {
this.stroke = stroke;
}
public float getStroke() {
return stroke;
}
public SignatureView(Context context) {
super(context);
init();
}
public SignatureView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public SignatureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public SignatureView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
//设置边界和bitmap的大小,注意:onLayout中一定可以获取到getWidth()和getHeight()
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
width=getWidth();
height=getHeight();
bitmap = Bitmap.createBitmap(width-bound, height-bound, Bitmap.Config.ARGB_8888);
myCanvas =new Canvas(bitmap);
boundary=new Rect(bound,bound,width-bound,height-bound);
}
private void init() {
path=new Path();
isdraw=false;
stroke=8;
bound=8;
setOnTouchListener(this);
}
//把之前的path和边框画出来
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint=new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.BLACK);
paint.setAntiAlias(true);
paint.setStrokeWidth(stroke);
canvas.drawPath(path,paint);
myCanvas.drawPath(path,paint);
canvas.drawRect(boundary,paint);
}
@Override
public boolean onTouch(View v, MotionEvent event) {
isdraw=true;
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
path.moveTo(event.getX(),event.getY());
invalidate();
break;
case MotionEvent.ACTION_MOVE:
path.lineTo(event.getX(),event.getY());
invalidate();
break;
}
return true;
}
public Bitmap getBitmap(){//返回bitmap
if(!isdraw)
return null;
return bitmap;
}
public void clear(){//清空画布
path.reset();
bitmap = Bitmap.createBitmap(width-bound, height-bound, Bitmap.Config.ARGB_8888);
myCanvas =new Canvas(bitmap);
invalidate();
}
}
2.具体实现,先写个activity_signature.xml布局
(注:布局仅供大家参考。重要的只有SignatureView的引用,引用时,找到自定义SignatureView所在项目的位置即可。)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_44"
android:background="@drawable/qiang_bg"
android:minHeight="@dimen/dp_44"
app:layout_collapseMode="pin"
app:title="">
<TextView
android:id="@+id/tvTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:singleLine="true"
android:text="手绘签名"
android:textColor="@android:color/white"
android:textSize="@dimen/sp_18" />
</androidx.appcompat.widget.Toolbar>
<ImageView
android:id="@+id/img"
android:layout_width="match_parent"
android:layout_height="200dp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.sun.SignatureView
android:id="@+id/view_sign"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<Button
android:id="@+id/btn_ok"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="确认"
android:textSize="@dimen/sp_18"/>
<Button
android:id="@+id/btn_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/btn_ok"
android:text="清除"
android:textSize="@dimen/sp_18"/>
</RelativeLayout>
</LinearLayout>
3.在Activity中的实现,写个SignatureActivity类
1)先初始化布局
(注:使用的是Butterknife,大家可以自己findViewById。)
@BindView(R.id.view_sign)
SignatureView view_sign;
@BindView(R.id.img)
ImageView imageView;
@BindView(R.id.btn_ok)
Button btn_ok;
@BindView(R.id.btn_clear)
Button btn_clear;
2)在Activity的onCreate()中实现功能
btn_ok.setOnClickListener(v -> {
//绘制到画板显示
imageView.setImageBitmap(view_sign.getBitmap());
//保存成图片,根据实际需求,决定是否调用此方法
savebitmap();
});
btn_clear.setOnClickListener(v -> {
view_sign.clear();
imageView.setImageBitmap(null);
});
3)savebitmap()方法写在Activity中即可。
//将bitmap保存到本地
public void savebitmap() {
Bitmap bitmap=view_sign.getBitmap();
//Android Q 10为每个应用程序提供了一个独立的在外部存储设备的存储沙箱,没有其他应用可以直接访问您应用的沙盒文件
File f = this.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File file=new File(f.getPath()+"/test.png");//创建文件,要保存png,这里后缀就是png,要保存jpg,后缀就用jpg
try {
//文件输出流
FileOutputStream fileOutputStream=new FileOutputStream(file);
//压缩图片,如果要保存png,就用Bitmap.CompressFormat.PNG,要保存jpg就用Bitmap.CompressFormat.JPEG,质量是100%,表示不压缩
bitmap.compress(Bitmap.CompressFormat.PNG,100,fileOutputStream);
//写入,这里会卡顿,因为图片较大
fileOutputStream.flush();
//记得要关闭写入流
fileOutputStream.close();
//成功的提示,写入成功后,请在对应目录中找保存的图片
Log.e("写入成功!目录:",f.getPath()+"/test.png");
} catch (FileNotFoundException e) {
e.printStackTrace();
//失败的提示
ToastUtil.showToast(e.getMessage());
} catch (IOException e) {
e.printStackTrace();
//失败的提示
ToastUtil.showToast(e.getMessage());
}
}
4)SignatureActivity类全部代码
(注:因继承了自定义的XBaseActivity,以下代码仅供参考,不必理会已经注释掉的代码)
import android.graphics.Bitmap;
import android.os.Environment;
import android.util.Log;
import android.widget.Button;
import android.widget.ImageView;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import butterknife.BindView;
public class SignatureActivity extends XBaseActivity {
@BindView(R.id.view_sign)
SignatureView view_sign;
@BindView(R.id.img)
ImageView imageView;
@BindView(R.id.btn_ok)
Button btn_ok;
@BindView(R.id.btn_clear)
Button btn_clear;
@Override
protected XBasePresenter createPresenter() {
return null;
}
@Override
protected int getLayoutId() {
return R.layout.activity_signature;
}
@Override
protected void initView() {
// 注意, 开发时用完一个Bitmap后,需要马上recycle()来保证尽快释放期资源。这里并没有处理, isRecycled() //判断位图内存是否已释放
btn_ok.setOnClickListener(v -> {
//绘制到画板显示
imageView.setImageBitmap(view_sign.getBitmap());
//保存成图片,根据实际需求,决定是否调用此方法
savebitmap();
});
btn_clear.setOnClickListener(v -> {
view_sign.clear();
imageView.setImageBitmap(null);
});
}
@Override
protected void initData() { }
//将bitmap保存到本地
public void savebitmap() {
//因为xml用的是背景,所以这里也是获得背景
// Bitmap bitmap=((BitmapDrawable)(imageView.getBackground())).getBitmap();
Bitmap bitmap = view_sign.getBitmap();
//创建文件,安卓低版本的方式
// File file=new File(Environment.getExternalStorageDirectory() +"/test.png");
//Android Q 10为每个应用程序提供了一个独立的在外部存储设备的存储沙箱,没有其他应用可以直接访问您应用的沙盒文件
File f = this.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File file = new File(f.getPath() + "/test.png");//创建文件,要保存png,这里后缀就是png,要保存jpg,后缀就用jpg
// file.getParentFile().mkdirs();
try {
//文件输出流
FileOutputStream fileOutputStream = new FileOutputStream(file);
//压缩图片,如果要保存png,就用Bitmap.CompressFormat.PNG,要保存jpg就用Bitmap.CompressFormat.JPEG,质量是100%,表示不压缩
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
//写入,这里会卡顿,因为图片较大
fileOutputStream.flush();
//记得要关闭写入流
fileOutputStream.close();
//成功的提示,写入成功后,请在对应目录中找保存的图片
Log.e("写入成功!目录", f.getPath() + "/test.png");
} catch (FileNotFoundException e) {
e.printStackTrace();
//失败的提示
ToastUtil.showToast(e.getMessage());
Log.e("失败====", e.getMessage());
} catch (IOException e) {
e.printStackTrace();
//失败的提示
ToastUtil.showToast(e.getMessage());
Log.e("失败2222====", e.getMessage());
}
}
}
版权声明:本文为博主原创文章,转载请点赞此文并注明出处,谢谢!
网友评论