因为信息化的需要,我所在公司的同事希望开发一款手机app用于到基层检查时对发现的违规现象进行现场处罚,代替纸质的罚单。软件的开发需求中有一项是要求检查单位和受检单位在手机上进行手写电子签名。正好我购买的欧阳燊先生编著的《Android App开发进阶与项目实战》一书中有关于实现手写电子签名的内容,正好借鉴。教材提供了相关的源码,不过需要根据我的项目实际修改一下。这篇日志记录下修改过程备忘。
先发一个实现后的效果截图。
一、参考资料
手写签名功能的实现,参考了《Android App开发进阶与项目实战》第2章 “2.2.3 跟踪滑动轨迹实现手写签名 ”的内容。自定义的手写签名控件代码在书中提供了,其他代码则在随教材提供的源码电子文档内。为了尊重作者的劳动成果,我就不贴出来了。
二、在自己的App中实现手写签名
教材中的“PathPosition.java”和“SignatureView.java”两个文件可以直接拿来使用。而用于实现手写签名的“SignatureActivity.java”和“activity_signature.xml”,需要根据我自己开发的需要进行修改。
修改的地方有以下点:
1.教材提供的源码功能和界面都有些繁杂,对于学习是好事,但对于实际应用这显得累赘,需要简化。我自己的手写签字界面只有自定义的SignatureView、清除按钮和提交按钮。
2.源代码点“结束签名”后,签字内容将显示在界面下方的ImageView中,点击“保存图片文件”将签字内容以JPG格式文件的形式保存到手机存储空间内,而我的app中要实现签字完成后点“提交按钮”将签字内容以Bitmap对象返回到调用“SignatureActivity.java”的活动中。
实现的效果:
在开具罚单的活动中,点击签字的位置,就会调用“SignatureActivity.java”,用户完成手写签字后,点“提交”按钮,就会将手写签名返回到开具罚单活动中。如下面两张图所示:
我修改后的“SignatureActivity.java”的代码
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.TextView;
import android.widget.Toast;
import com.bahamutj.penalty.widget.SignatureView;
import java.io.ByteArrayOutputStream;
public class SignatureActivity extends AppCompatActivity implements OnClickListener {
private final static String TAG = "SignatureActivity";
private SignatureView view_signature; // 声明一个签名视图对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_signature);
TextView tv_title = findViewById(R.id.tv_title); // 标题
tv_title.setText(this.getString(R.string.SignatureActivity_title)); // 设置标题名称
view_signature = findViewById(R.id.view_signature); // 签字区
view_signature.setDrawingCacheEnabled(true); // 设置缓存区域为可用
findViewById(R.id.iv_back).setOnClickListener(this); // 返回
findViewById(R.id.btn_clear_signature).setOnClickListener(this); // 清除
findViewById(R.id.btn_complete_signature).setOnClickListener(this); // 提交
} // onCreate-end
@Override
public void onClick(View v) {
if (v.getId() == R.id.iv_back) { // 点击的返回图标
finish(); // 结束当前的活动页面
} else if (v.getId() == R.id.btn_clear_signature) { // 点击了清除按钮
view_signature.clear(); // 清空签名视图
} else if (v.getId() == R.id.btn_complete_signature) { // 点击了提交按钮
int mPathListSize = view_signature.getPathListSize(); // 获取绘制的路径列表大小,用于判定
if (mPathListSize == 0) { // 等于0说明没有绘制路径
Toast.makeText(this, "请签名后再提交", Toast.LENGTH_LONG).show();
return;
}
Bitmap bitmap = view_signature.getDrawingCache(); // 从绘图缓存获取位图对象
if ( (bitmap == null) || (bitmap.isRecycled())) { // 对象为空则不提交
Toast.makeText(this, "请签名后再提交", Toast.LENGTH_LONG).show();
return;
}
Intent intent = new Intent();
// 把bitmap存储为byte数组,图像数据来源于签名视图对象,数据不用保存为文件
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
byte[] bytes = baos.toByteArray();
// 往包裹存入byte数组
intent.putExtra("bitmapbytes",bytes);
// 携带意图返回上一个页面。RESULT_OK表示处理成功
setResult(Activity.RESULT_OK, intent);
finish();
}
} // onClick-end
}
我修改后的“activity_signature.xml”布局文件
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/gray_245"
tools:context=".SignatureActivity">
<include layout="@layout/title_bar"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="20dp"
android:text="请在下方签字区内手写签字"
android:textColor="@color/gray_150"
android:textSize="16sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<com.bahamutj.penalty.widget.SignatureView
android:id="@+id/view_signature"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@color/white"
app:paint_color="#000000"
app:stroke_width="10" />
<!-- 可在此修改颜色和线宽 -->
<!-- 颜色和和线宽是在attrs.xml中自定义的 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginTop="20dp"
android:orientation="horizontal" >
<Button
android:id="@+id/btn_clear_signature"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/signature_btn_clear"
android:textColor="@color/white"
android:backgroundTint="@color/blue_415"
android:textSize="17sp"
android:layout_marginEnd="20dp"
style="?android:attr/buttonBarButtonStyle" />
<Button
android:id="@+id/btn_complete_signature"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/signature_btn_complete"
android:textColor="@color/white"
android:backgroundTint="@color/blue_415"
android:textSize="17sp"
android:layout_marginStart="20dp"
style="?android:attr/buttonBarButtonStyle" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
开具处罚单活动中调用“SignatureActivity.java”的关键代码
public class PenaltyActivity extends AppCompatActivity implements
View.OnClickListener, DatePickerDialog.OnDateSetListener {
private final int INSPECTED_CODE = 1; // 被检查人签名意图代码
private final int INSPECTOR_CODE = 2; // 检查人签名意图代码
private ImageView iv_Inspected_photo, iv_Inspector_photo; // 声明两个图像视图对象保存被检查人签名和检查人签名
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_penalty);
iv_Inspected_photo = findViewById(R.id.iv_Inspected_photo); // 被检查单位(人)签名
iv_Inspector_photo = findViewById(R.id.iv_Inspector_photo); // 检查人签名
// 设置监听器
findViewById(R.id.iv_Inspected_photo).setOnClickListener(this); // 获取被检查单位(人)签字
findViewById(R.id.iv_Inspector_photo).setOnClickListener(this); // 获取检查人签字
@Override
public void onClick(View v) {
if (v.getId() == R.id.iv_Inspected_photo) { // 被检查单位(人)签名
Intent intent = new Intent(this, SignatureActivity.class);
startActivityForResult(intent, INSPECTED_CODE); // 获取被检查单位(人)手写签名
} else if (v.getId() == R.id.iv_Inspector_photo) { // 被检查人签名
Intent intent = new Intent(this, SignatureActivity.class);
startActivityForResult(intent, INSPECTOR_CODE); // 获取检查人手写签名
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (intent!=null && resultCode == RESULT_OK && requestCode == INSPECTED_CODE) {
// 取回被检查单位(人)手写签名
byte[] bytes =intent.getByteArrayExtra("bitmapbytes");
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
iv_Inspected_photo.setImageBitmap(bitmap);
} else if (intent!=null && resultCode == RESULT_OK && requestCode == INSPECTOR_CODE) {
// 取回检查人手写签名
byte[] bytes =intent.getByteArrayExtra("bitmapbytes");
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
iv_Inspector_photo.setImageBitmap(bitmap);
}
}
}