问题
在 Android 应用中,如何实现签名功能?
回答
可以借助 signature-pad 库提供的 SignaturePad
控件来实现。
效果
简介
Android Signature Pad is an Android library for drawing smooth signatures. It uses variable width Bézier curve interpolation based on Smoother Signatures post by Square.
译:Android Signature Pad 是一个用于绘制签名的 Android 库。它基于 Square 发布的 Smoother Signatures 使用可变宽度的贝塞尔插值。
Features
- Bézier implementation for a smoother line
- Variable point size based on velocity
- Customizable pen color and size
- Bitmap and SVG support
- Data Binding
译:
特性
- 贝塞尔插值绘制出更光滑的线条
- 根据速率变化的线条粗细
- 自定义画笔颜色和大小
- 支持 Bitmap 和 SVG
- 数据绑定 API
注:下文代码是参考库的开发者的示例代码而写出的。如需进一步了解,请移步 SignaturePad-Example 。
步骤
- 添加 signature-pad 库的依赖。
dependencies {
...
implementation 'com.github.gcacace:signature-pad:1.3.1'
...
}
- 在 layout 文件中使用
SignaturePad
控件,另外添加“清空”和“保存”两个按钮。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="listener"
type="pers.xumeng.androidstudy.signature.SignatureActivity" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorActivityBackground">
<TextView
android:id="@+id/signature_tv_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="请在下方签名"
android:textColor="@color/color_333333"
android:textSize="@dimen/text_size3"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.2" />
<com.github.gcacace.signaturepad.views.SignaturePad
android:id="@+id/signature_s_signature"
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_margin="@dimen/common_margin"
android:background="@drawable/rounded_rectangle_corner_10_color_white"
android:padding="2dp"
app:layout_constraintBottom_toTopOf="@id/signature_ll_button_bar"
app:layout_constraintTop_toBottomOf="@id/signature_tv_message" />
<LinearLayout
android:id="@+id/signature_ll_button_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/common_padding"
app:layout_constraintBottom_toBottomOf="parent">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="@dimen/common_margin"
android:layout_weight="1"
android:background="@drawable/rounded_rectangle_corner_10_color_primary_border"
android:onClick="@{()->listener.clearSignature()}"
android:text="清空"
android:textColor="@color/colorPrimary"
android:textSize="@dimen/text_size3" />
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:background="@drawable/rounded_rectangle_corner_10_color_primary"
android:onClick="@{()->listener.attemptSaveSignature()}"
android:text="保存"
android:textColor="@android:color/white"
android:textSize="@dimen/text_size3" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
- 实现清空
SignaturePad
控件内容的功能,在按钮的监听器中调用signaturepad.clear()
方法即可。
public void clearSignature() {
binding.signatureSSignature.clear();
}
- 实现保存
SignaturePad
控件内容的功能:将控件上的内容保存为.jpg
文件,并添加到相册。
private void saveSignature() {
Bitmap signatureBitmap = binding.signatureSSignature.getSignatureBitmap();
if (addJpgSignatureToGallery(signatureBitmap)) {
Toast.makeText(this, "保存成功", Toast.LENGTH_SHORT).show();
clearSignature();
} else {
Toast.makeText(this, "保存失败", Toast.LENGTH_SHORT).show();
}
}
4.1 检查外部存储权限,这一部分超出了本文的介绍范围。
4.2 将 Bitmap 保存为 .jpg 图片,并添加到相册。
private boolean addJpgSignatureToGallery(Bitmap signature) {
boolean result = false;
try {
String fileName = String.format(Locale.CHINA, "Signature_%d.jpg", System.currentTimeMillis());
File photo = new File(createAlbumStorageDirectory(), fileName);
saveBitmapToJPG(signature, photo);
scanMediaFile(photo);
result = true;
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
- 创建图片的保存目录
private File createAlbumStorageDirectory() {
// Get the directory for the user's public pictures directory.
File parentDirectory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File directory = new File(parentDirectory, "Signature");
boolean successful = directory.mkdirs();
if (!successful) {
LogUtil.e(TAG, "Directory not created");
}
return directory;
}
请注意,Environment.getExternalStoragePublicDirectory(String type)
在 API 29 被弃用了。如需了解详细信息,请参阅 getExternalStoragePublicDirectory 。
- 将 Bitmap 保存为 .jpg 文件
private void saveBitmapToJPG(Bitmap bitmap, File photo) throws IOException {
Bitmap newBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(newBitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(bitmap, 0, 0, null);
OutputStream stream = new FileOutputStream(photo);
newBitmap.compress(Bitmap.CompressFormat.JPEG, 80, stream);
stream.close();
}
- 通知扫描器将图片添加到媒体库
private void scanMediaFile(File photo) {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
Uri contentUri = Uri.fromFile(photo);
mediaScanIntent.setData(contentUri);
sendBroadcast(mediaScanIntent);
}
需要注意的是, Intent.ACTION_MEDIA_SCANNER_SCAN_FILE
在 API 29 被弃用了,替代方案是使用 MediaStore 。如需进一步了解,请参阅 ACTION_MEDIA_SCANNER_SCAN_FILE 。