Skip to content

Instantly share code, notes, and snippets.

@vencil
Last active March 27, 2018 07:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vencil/74dea8764f10afb84c2ad02e71a49f4a to your computer and use it in GitHub Desktop.
Save vencil/74dea8764f10afb84c2ad02e71a49f4a to your computer and use it in GitHub Desktop.
A custom Android View to capture a user's signature
/**
* Copyright (C) 2016 by Vencil(vencsvencil@gmail.com)
* Base canvas action is written by Matthew Silber (https://github.com/mattsilber/sigcap), Apache 2.0 licensed.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Base64;
import android.view.MotionEvent;
import android.view.View;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
public class SignatureView extends View implements View.OnTouchListener {
private List<List<Path>> signaturePaths = new ArrayList<>();
private List<Path> activeSignaturePaths;
private Paint signaturePaint;
private Paint baselinePaint;
private int baselinePaddingHorizontal = 0;
private int baselinePaddingBottom = 0;
private int baselineXMark = 0;
private int baselineXMarkOffsetVertical = 0;
private int[] lastTouchEvent;
public SignatureView(Context context) {
super(context);
initDefaultValues();
}
public SignatureView(Context context, AttributeSet attrs) {
super(context, attrs);
initDefaultValues();
}
public SignatureView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initDefaultValues();
}
protected void initDefaultValues() {
setOnTouchListener(this);
signaturePaint = new Paint();
signaturePaint.setAntiAlias(true);
signaturePaint.setColor(Color.BLACK);
signaturePaint.setStyle(Paint.Style.FILL_AND_STROKE);
signaturePaint.setStrokeWidth(15f);
signaturePaint.setStrokeCap(Paint.Cap.ROUND);
baselinePaint = new Paint();
baselinePaint.setAntiAlias(true);
baselinePaint.setColor(Color.BLACK);
baselinePaint.setStyle(Paint.Style.FILL_AND_STROKE);
baselinePaint.setStrokeWidth(1f);
baselinePaint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
public boolean onTouch(View view, MotionEvent e) {
int[] event = new int[]{(int) e.getX(), (int) e.getY()};
if (e.getAction() == MotionEvent.ACTION_UP) {
lastTouchEvent = null;
signaturePaths.add(activeSignaturePaths);
activeSignaturePaths = new ArrayList<>();
} else if (e.getAction() == MotionEvent.ACTION_DOWN) {
if (!(activeSignaturePaths == null || activeSignaturePaths.size() < 1))
signaturePaths.add(activeSignaturePaths);
activeSignaturePaths = new ArrayList<>();
} else {
Path path = new Path();
path.moveTo(lastTouchEvent[0], lastTouchEvent[1]);
path.lineTo(event[0], event[1]);
activeSignaturePaths.add(path);
}
lastTouchEvent = event;
postInvalidate();
return true;
}
public void undoLastSignaturePath() {
if (0 < signaturePaths.size()) {
signaturePaths.remove(signaturePaths.size() - 1);
postInvalidate();
}
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawIndicators(canvas);
drawSignaturePaths(canvas);
}
protected void drawIndicators(Canvas canvas) {
canvas.drawLine(baselinePaddingHorizontal,
canvas.getHeight() - baselinePaddingBottom,
canvas.getWidth() - baselinePaddingHorizontal,
canvas.getHeight() - baselinePaddingBottom,
baselinePaint);
drawXMark(canvas);
}
protected void drawXMark(Canvas canvas) {
int radius = baselineXMark / 2;
int cX = baselinePaddingHorizontal + radius;
int cY = canvas.getHeight() - baselinePaddingBottom - radius - baselineXMarkOffsetVertical;
canvas.save();
canvas.rotate(-45, cX, cY);
canvas.drawLine(cX - radius, cY, cX + radius, cY, baselinePaint);
canvas.restore();
canvas.save();
canvas.rotate(45, cX, cY);
canvas.drawLine(cX - radius, cY, cX + radius, cY, baselinePaint);
canvas.restore();
}
protected void drawSignaturePaths(Canvas canvas) {
for (List<Path> l : signaturePaths) {
for (Path p : l) {
canvas.drawPath(p, signaturePaint);
}
}
if (activeSignaturePaths != null) {
for (Path p : activeSignaturePaths) {
canvas.drawPath(p, signaturePaint);
}
}
}
public String getContentDataURI() {
setDrawingCacheEnabled(true);
Bitmap bitmap = getDrawingCache();
Bitmap cropBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight() - 1);
bitmap.recycle();
Bitmap resizedBitmap = Bitmap.createScaledBitmap(cropBitmap, 300, 80, false);
cropBitmap.recycle();
ByteArrayOutputStream stream = new ByteArrayOutputStream();
resizedBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
setDrawingCacheEnabled(false);
return "data:image/png;base64," + Base64.encodeToString(baos.toByteArray(), Base64.NO_WRAP);
}
}
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.view.ViewGroup;
public class SignatureViewDemoDialog extends DialogFragment {
public static SignatureViewDemoDialog newInstance(Fragment caller) {
SignatureViewDemoDialog demoDialog = new SignatureViewDemoDialog();
demoDialog.setTargetFragment(caller, 0);
return demoDialog;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Context context = getActivity();
final SignatureView signatureView = new SignatureView(context, null);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), R.style.MaterialDialog);
builder.setTitle("Signature demo")
.setView(signatureView)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String imageDataURI = signatureView.getContentDataURI();
// TODO data storage here
}
});
return builder.create();
}
@Override
public void onCreate(Bundle icicle) {
setCancelable(true);
setRetainInstance(true);
super.onCreate(icicle);
}
@Override
public void onResume() {
ViewGroup.LayoutParams params = getDialog().getWindow().getAttributes();
params.height = 1000;
getDialog().getWindow().setAttributes((android.view.WindowManager.LayoutParams) params);
super.onResume();
}
@Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance()) {
getDialog().setDismissMessage(null);
}
super.onDestroyView();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment