Convert java file into kotlin

This commit is contained in:
Shankar Yadav
2021-10-29 18:49:12 +05:30
parent a7be23a3d2
commit 03322d3a41
18 changed files with 884 additions and 978 deletions

View File

@@ -1,14 +0,0 @@
package com.navi.amc.investorapp.signature.drawerControllers;
public class ControlTimedPoints {
public TimedPoint c1;
public TimedPoint c2;
public ControlTimedPoints set(TimedPoint c1, TimedPoint c2) {
this.c1 = c1;
this.c2 = c2;
return this;
}
}

View File

@@ -0,0 +1,11 @@
package com.navi.amc.investorapp.signature.drawerControllers
class ControlTimedPoints {
var c1: TimedPoint? = null
var c2: TimedPoint? = null
fun set(c1: TimedPoint?, c2: TimedPoint?): ControlTimedPoints {
this.c1 = c1
this.c2 = c2
return this
}
}

View File

@@ -1,49 +0,0 @@
package com.navi.amc.investorapp.signature.drawerControllers;
public class CurveBezier {
public TimedPoint startPoint;
public TimedPoint control1;
public TimedPoint control2;
public TimedPoint endPoint;
public CurveBezier set(TimedPoint startPoint, TimedPoint control1,
TimedPoint control2, TimedPoint endPoint) {
this.startPoint = startPoint;
this.control1 = control1;
this.control2 = control2;
this.endPoint = endPoint;
return this;
}
public float length() {
int steps = 10;
float length = 0;
double cx, cy, px = 0, py = 0, xDiff, yDiff;
for (int i = 0; i <= steps; i++) {
float t = (float) i / steps;
cx = point(t, this.startPoint.x, this.control1.x,
this.control2.x, this.endPoint.x);
cy = point(t, this.startPoint.y, this.control1.y,
this.control2.y, this.endPoint.y);
if (i > 0) {
xDiff = cx - px;
yDiff = cy - py;
length += Math.sqrt(xDiff * xDiff + yDiff * yDiff);
}
px = cx;
py = cy;
}
return length;
}
public double point(float t, float start, float c1, float c2, float end) {
return start * (1.0 - t) * (1.0 - t) * (1.0 - t)
+ 3.0 * c1 * (1.0 - t) * (1.0 - t) * t
+ 3.0 * c2 * (1.0 - t) * t * t
+ end * t * t * t;
}
}

View File

@@ -0,0 +1,52 @@
package com.navi.amc.investorapp.signature.drawerControllers
class CurveBezier {
var startPoint: TimedPoint? = null
var control1: TimedPoint? = null
var control2: TimedPoint? = null
var endPoint: TimedPoint? = null
operator fun set(
startPoint: TimedPoint?, control1: TimedPoint?,
control2: TimedPoint?, endPoint: TimedPoint?
): CurveBezier {
this.startPoint = startPoint
this.control1 = control1
this.control2 = control2
this.endPoint = endPoint
return this
}
fun length(): Float {
val steps = 10
var length = 0f
var cx: Double
var cy: Double
var px = 0.0
var py = 0.0
var xDiff: Double
var yDiff: Double
for (i in 0..steps) {
val t = i.toFloat() / steps
cx = point(
t, startPoint!!.x, control1!!.x,
control2!!.x, endPoint!!.x
)
cy = point(
t, startPoint!!.y, control1!!.y,
control2!!.y, endPoint!!.y
)
if (i > 0) {
xDiff = cx - px
yDiff = cy - py
length += Math.sqrt(xDiff * xDiff + yDiff * yDiff).toFloat()
}
px = cx
py = cy
}
return length
}
fun point(t: Float, start: Float, c1: Float, c2: Float, end: Float): Double {
return start * (1.0 - t) * (1.0 - t) * (1.0 - t) + 3.0 * c1 * (1.0 - t) * (1.0 - t) * t + 3.0 * c2 * (1.0 - t) * t * t + end * t * t * t
}
}

View File

@@ -1,24 +0,0 @@
package com.navi.amc.investorapp.signature.drawerControllers;
public class TimedPoint {
public float x;
public float y;
public long timestamp;
public TimedPoint set(float x, float y) {
this.x = x;
this.y = y;
this.timestamp = System.currentTimeMillis();
return this;
}
public float velocityFrom(TimedPoint start) {
float velocity = distanceTo(start) / (this.timestamp - start.timestamp);
if (velocity != velocity) return 0f;
return velocity;
}
public float distanceTo(TimedPoint point) {
return (float) Math.sqrt(Math.pow(point.x - this.x, 2) + Math.pow(point.y - this.y, 2));
}
}

View File

@@ -0,0 +1,31 @@
package com.navi.amc.investorapp.signature.drawerControllers
import com.navi.amc.investorapp.util.orZero
class TimedPoint {
@JvmField
var x = 0f
@JvmField
var y = 0f
var timestamp: Long = 0
operator fun set(x: Float, y: Float): TimedPoint {
this.x = x
this.y = y
timestamp = System.currentTimeMillis()
return this
}
fun velocityFrom(start: TimedPoint?): Float {
val velocity = distanceTo(start) / (timestamp - start?.timestamp.orZero())
return if (velocity != velocity) 0f else velocity
}
fun distanceTo(point: TimedPoint?): Float {
return Math.sqrt(
Math.pow((point?.x.orZero() - x).toDouble(), 2.0) + Math.pow(
(point?.y.orZero() - y).toDouble(),
2.0
)
).toFloat()
}
}

View File

@@ -1,76 +0,0 @@
package com.navi.amc.investorapp.signature.svgUtils;
import com.navi.amc.investorapp.signature.drawerControllers.CurveBezier;
public class SvgBuilder {
private final StringBuilder mSvgPathsBuilder = new StringBuilder();
private SvgPathBuilder mCurrentPathBuilder = null;
public SvgBuilder() {
}
public void clear() {
mSvgPathsBuilder.setLength(0);
mCurrentPathBuilder = null;
}
public String build(final int width, final int height) {
if (isPathStarted()) {
appendCurrentPath();
}
return (new StringBuilder())
.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n")
.append("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.2\" baseProfile=\"tiny\" ")
.append("height=\"")
.append(height)
.append("\" ")
.append("width=\"")
.append(width)
.append("\">")
.append("<g ")
.append("stroke-linejoin=\"round\" ")
.append("stroke-linecap=\"round\" ")
.append("fill=\"none\" ")
.append("stroke=\"black\"")
.append(">")
.append(mSvgPathsBuilder)
.append("</g>")
.append("</svg>")
.toString();
}
public SvgBuilder append(final CurveBezier curve, final float strokeWidth) {
final Integer roundedStrokeWidth = Math.round(strokeWidth);
final SvgPoint curveStartSvgPoint = new SvgPoint(curve.startPoint);
final SvgPoint curveControlSvgPoint1 = new SvgPoint(curve.control1);
final SvgPoint curveControlSvgPoint2 = new SvgPoint(curve.control2);
final SvgPoint curveEndSvgPoint = new SvgPoint(curve.endPoint);
if (!isPathStarted()) {
startNewPath(roundedStrokeWidth, curveStartSvgPoint);
}
if (!curveStartSvgPoint.equals(mCurrentPathBuilder.getLastPoint())
|| !roundedStrokeWidth.equals(mCurrentPathBuilder.getStrokeWidth())) {
appendCurrentPath();
startNewPath(roundedStrokeWidth, curveStartSvgPoint);
}
mCurrentPathBuilder.append(curveControlSvgPoint1, curveControlSvgPoint2, curveEndSvgPoint);
return this;
}
private void startNewPath(Integer roundedStrokeWidth, SvgPoint curveStartSvgPoint) {
mCurrentPathBuilder = new SvgPathBuilder(curveStartSvgPoint, roundedStrokeWidth);
}
private void appendCurrentPath() {
mSvgPathsBuilder.append(mCurrentPathBuilder);
}
private boolean isPathStarted() {
return mCurrentPathBuilder != null;
}
}

View File

@@ -0,0 +1,68 @@
package com.navi.amc.investorapp.signature.svgUtils
import com.navi.amc.investorapp.signature.drawerControllers.CurveBezier
import java.lang.StringBuilder
class SvgBuilder {
private val mSvgPathsBuilder = StringBuilder()
private var mCurrentPathBuilder: SvgPathBuilder? = null
fun clear() {
mSvgPathsBuilder.setLength(0)
mCurrentPathBuilder = null
}
fun build(width: Int, height: Int): String {
if (isPathStarted) {
appendCurrentPath()
}
return StringBuilder()
.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n")
.append("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.2\" baseProfile=\"tiny\" ")
.append("height=\"")
.append(height)
.append("\" ")
.append("width=\"")
.append(width)
.append("\">")
.append("<g ")
.append("stroke-linejoin=\"round\" ")
.append("stroke-linecap=\"round\" ")
.append("fill=\"none\" ")
.append("stroke=\"black\"")
.append(">")
.append(mSvgPathsBuilder)
.append("</g>")
.append("</svg>")
.toString()
}
fun append(curve: CurveBezier, strokeWidth: Float): SvgBuilder {
val roundedStrokeWidth = Math.round(strokeWidth)
val curveStartSvgPoint = SvgPoint(curve.startPoint)
val curveControlSvgPoint1 = SvgPoint(curve.control1)
val curveControlSvgPoint2 = SvgPoint(curve.control2)
val curveEndSvgPoint = SvgPoint(curve.endPoint)
if (!isPathStarted) {
startNewPath(roundedStrokeWidth, curveStartSvgPoint)
}
if (curveStartSvgPoint != mCurrentPathBuilder!!.lastPoint
|| roundedStrokeWidth != mCurrentPathBuilder!!.strokeWidth
) {
appendCurrentPath()
startNewPath(roundedStrokeWidth, curveStartSvgPoint)
}
mCurrentPathBuilder!!.append(curveControlSvgPoint1, curveControlSvgPoint2, curveEndSvgPoint)
return this
}
private fun startNewPath(roundedStrokeWidth: Int, curveStartSvgPoint: SvgPoint) {
mCurrentPathBuilder = SvgPathBuilder(curveStartSvgPoint, roundedStrokeWidth)
}
private fun appendCurrentPath() {
mSvgPathsBuilder.append(mCurrentPathBuilder)
}
private val isPathStarted: Boolean
private get() = mCurrentPathBuilder != null
}

View File

@@ -1,73 +0,0 @@
package com.navi.amc.investorapp.signature.svgUtils;
public class SvgPathBuilder {
public static final Character SVG_RELATIVE_CUBIC_BEZIER_CURVE = 'c';
public static final Character SVG_MOVE = 'M';
private final StringBuilder mStringBuilder;
private final Integer mStrokeWidth;
private final SvgPoint mStartPoint;
private SvgPoint mLastPoint;
public SvgPathBuilder(final SvgPoint startPoint, final Integer strokeWidth) {
mStrokeWidth = strokeWidth;
mStartPoint = startPoint;
mLastPoint = startPoint;
mStringBuilder = new StringBuilder();
mStringBuilder.append(SVG_RELATIVE_CUBIC_BEZIER_CURVE);
}
public final Integer getStrokeWidth() {
return mStrokeWidth;
}
public final SvgPoint getLastPoint() {
return mLastPoint;
}
public SvgPathBuilder append(final SvgPoint controlPoint1, final SvgPoint controlPoint2, final SvgPoint endPoint) {
mStringBuilder.append(makeRelativeCubicBezierCurve(controlPoint1, controlPoint2, endPoint));
mLastPoint = endPoint;
return this;
}
@Override
public String toString() {
return (new StringBuilder())
.append("<path ")
.append("stroke-width=\"")
.append(mStrokeWidth)
.append("\" ")
.append("d=\"")
.append(SVG_MOVE)
.append(mStartPoint)
.append(mStringBuilder)
.append("\"/>")
.toString();
}
private String makeRelativeCubicBezierCurve(final SvgPoint controlPoint1, final SvgPoint controlPoint2, final SvgPoint endPoint) {
final String sControlPoint1 = controlPoint1.toRelativeCoordinates(mLastPoint);
final String sControlPoint2 = controlPoint2.toRelativeCoordinates(mLastPoint);
final String sEndPoint = endPoint.toRelativeCoordinates(mLastPoint);
final StringBuilder sb = new StringBuilder();
sb.append(sControlPoint1);
sb.append(" ");
sb.append(sControlPoint2);
sb.append(" ");
sb.append(sEndPoint);
sb.append(" ");
// discard zero curve
final String svg = sb.toString();
if ("c0 0 0 0 0 0".equals(svg)) {
return "";
} else {
return svg;
}
}
}

View File

@@ -0,0 +1,69 @@
package com.navi.amc.investorapp.signature.svgUtils
import java.lang.StringBuilder
class SvgPathBuilder(private val mStartPoint: SvgPoint, val strokeWidth: Int) {
private val mStringBuilder: StringBuilder
var lastPoint: SvgPoint
private set
fun append(
controlPoint1: SvgPoint,
controlPoint2: SvgPoint,
endPoint: SvgPoint
): SvgPathBuilder {
mStringBuilder.append(makeRelativeCubicBezierCurve(controlPoint1, controlPoint2, endPoint))
lastPoint = endPoint
return this
}
override fun toString(): String {
return StringBuilder()
.append("<path ")
.append("stroke-width=\"")
.append(strokeWidth)
.append("\" ")
.append("d=\"")
.append(SVG_MOVE)
.append(mStartPoint)
.append(mStringBuilder)
.append("\"/>")
.toString()
}
private fun makeRelativeCubicBezierCurve(
controlPoint1: SvgPoint,
controlPoint2: SvgPoint,
endPoint: SvgPoint
): String {
val sControlPoint1 = controlPoint1.toRelativeCoordinates(lastPoint)
val sControlPoint2 = controlPoint2.toRelativeCoordinates(lastPoint)
val sEndPoint = endPoint.toRelativeCoordinates(lastPoint)
val sb = StringBuilder()
sb.append(sControlPoint1)
sb.append(" ")
sb.append(sControlPoint2)
sb.append(" ")
sb.append(sEndPoint)
sb.append(" ")
// discard zero curve
val svg = sb.toString()
return if ("c0 0 0 0 0 0" == svg) {
""
} else {
svg
}
}
companion object {
const val SVG_RELATIVE_CUBIC_BEZIER_CURVE = 'c'
const val SVG_MOVE = 'M'
}
init {
lastPoint = mStartPoint
mStringBuilder = StringBuilder()
mStringBuilder.append(SVG_RELATIVE_CUBIC_BEZIER_CURVE)
}
}

View File

@@ -1,56 +0,0 @@
package com.navi.amc.investorapp.signature.svgUtils;
import com.navi.amc.investorapp.signature.drawerControllers.TimedPoint;
class SvgPoint {
final Integer x, y;
public SvgPoint(TimedPoint point) {
// one optimisation is to get rid of decimals as they are mostly non-significant in the
// produced SVG image
x = Math.round(point.x);
y = Math.round(point.y);
}
public SvgPoint(int x, int y) {
this.x = x;
this.y = y;
}
public String toAbsoluteCoordinates() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(x);
stringBuilder.append(",");
stringBuilder.append(y);
return stringBuilder.toString();
}
public String toRelativeCoordinates(final SvgPoint referencePoint) {
return (new SvgPoint(x - referencePoint.x, y - referencePoint.y)).toString();
}
@Override
public String toString() {
return toAbsoluteCoordinates();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SvgPoint svgPoint = (SvgPoint) o;
if (!x.equals(svgPoint.x)) return false;
return y.equals(svgPoint.y);
}
@Override
public int hashCode() {
int result = x.hashCode();
result = 31 * result + y.hashCode();
return result;
}
}

View File

@@ -0,0 +1,52 @@
package com.navi.amc.investorapp.signature.svgUtils
import com.navi.amc.investorapp.signature.drawerControllers.TimedPoint
import com.navi.amc.investorapp.signature.svgUtils.SvgPoint
import com.navi.amc.investorapp.util.orZero
import java.lang.StringBuilder
class SvgPoint {
val x: Int
val y: Int
constructor(point: TimedPoint?) {
// one optimisation is to get rid of decimals as they are mostly non-significant in the
// produced SVG image
x = Math.round(point?.x.orZero())
y = Math.round(point?.y.orZero())
}
constructor(x: Int, y: Int) {
this.x = x
this.y = y
}
fun toAbsoluteCoordinates(): String {
val stringBuilder = StringBuilder()
stringBuilder.append(x)
stringBuilder.append(",")
stringBuilder.append(y)
return stringBuilder.toString()
}
fun toRelativeCoordinates(referencePoint: SvgPoint?): String {
return SvgPoint(x - referencePoint?.x.orZero(), y - referencePoint?.y.orZero()).toString()
}
override fun toString(): String {
return toAbsoluteCoordinates()
}
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o == null || javaClass != o.javaClass) return false
val svgPoint = o as SvgPoint
return if (x != svgPoint.x) false else y == svgPoint.y
}
override fun hashCode(): Int {
var result = x.hashCode()
result = 31 * result + y.hashCode()
return result
}
}

View File

@@ -1,651 +0,0 @@
package com.navi.amc.investorapp.signature.ui;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import com.navi.amc.investorapp.R;
import com.navi.amc.investorapp.signature.drawerControllers.ControlTimedPoints;
import com.navi.amc.investorapp.signature.drawerControllers.CurveBezier;
import com.navi.amc.investorapp.signature.drawerControllers.TimedPoint;
import com.navi.amc.investorapp.signature.svgUtils.SvgBuilder;
import com.navi.amc.investorapp.signature.viewHelper.ViewCompat;
import com.navi.amc.investorapp.signature.viewHelper.ViewTreeObserverCompat;
import java.util.ArrayList;
import java.util.List;
public class SilkySignaturePad extends View {
//View state
private List<TimedPoint> mPoints;
private boolean mIsEmpty;
private float mLastTouchX;
private float mLastTouchY;
private float mLastVelocity;
private float mLastWidth;
private RectF mDirtyRect;
private final SvgBuilder mSvgBuilder = new SvgBuilder();
// Cache
private List<TimedPoint> mPointsCache = new ArrayList<>();
private ControlTimedPoints mControlTimedPointsCached = new ControlTimedPoints();
private CurveBezier mCurveBezierCached = new CurveBezier();
//Configurable parameters
private int mMinWidth;
private int mMaxWidth;
private float mVelocityFilterWeight;
private OnSignedListener mOnSignedListener;
private boolean mClearOnDoubleClick;
//Click values
private long mFirstClick;
private int mCountClick;
private static final int DOUBLE_CLICK_DELAY_MS = 200;
//Default attribute values
private final int DEFAULT_ATTR_PEN_MIN_WIDTH_PX = 3;
private final int DEFAULT_ATTR_PEN_MAX_WIDTH_PX = 7;
private final int DEFAULT_ATTR_PEN_COLOR = Color.BLACK;
private final float DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT = 0.9f;
private final boolean DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK = false;
private Paint mPaint = new Paint();
private Bitmap mSignatureBitmap = null;
private Canvas mSignatureBitmapCanvas = null;
public SilkySignaturePad(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.SilkySignaturePad,
0, 0);
//Configurable parameters
try {
mMinWidth = a.getDimensionPixelSize(R.styleable.SilkySignaturePad_penMinWidth, convertDpToPx(DEFAULT_ATTR_PEN_MIN_WIDTH_PX));
mMaxWidth = a.getDimensionPixelSize(R.styleable.SilkySignaturePad_penMaxWidth, convertDpToPx(DEFAULT_ATTR_PEN_MAX_WIDTH_PX));
mPaint.setColor(a.getColor(R.styleable.SilkySignaturePad_penColor, DEFAULT_ATTR_PEN_COLOR));
mVelocityFilterWeight = a.getFloat(R.styleable.SilkySignaturePad_velocityFilterWeight, DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT);
mClearOnDoubleClick = a.getBoolean(R.styleable.SilkySignaturePad_clearOnDoubleClick, DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK);
} finally {
a.recycle();
}
//Fixed parameters
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mPaint.setStrokeJoin(Paint.Join.ROUND);
//Dirty rectangle to update only the changed portion of the view
mDirtyRect = new RectF();
clear();
}
/**
* Set the pen color from a given resource.
* If the resource is not found, {@link Color#BLACK} is assumed.
*
* @param colorRes the color resource.
*/
public void setPenColorRes(int colorRes) {
try {
setPenColor(getResources().getColor(colorRes));
} catch (Resources.NotFoundException ex) {
setPenColor(Color.parseColor("#000000"));
}
}
/**
* Set the pen color from a given color.
*
* @param color the color.
*/
public void setPenColor(int color) {
mPaint.setColor(color);
}
/**
* Set the minimum width of the stroke in pixel.
*
* @param minWidth the width in dp.
*/
public void setMinWidth(float minWidth) {
mMinWidth = convertDpToPx(minWidth);
}
/**
* Set the maximum width of the stroke in pixel.
*
* @param maxWidth the width in dp.
*/
public void setMaxWidth(float maxWidth) {
mMaxWidth = convertDpToPx(maxWidth);
}
/**
* Set the velocity filter weight.
*
* @param velocityFilterWeight the weight.
*/
public void setVelocityFilterWeight(float velocityFilterWeight) {
mVelocityFilterWeight = velocityFilterWeight;
}
public void clear() {
mSvgBuilder.clear();
mPoints = new ArrayList<>();
mLastVelocity = 0;
mLastWidth = (mMinWidth + mMaxWidth) / 2;
if (mSignatureBitmap != null) {
mSignatureBitmap = null;
ensureSignatureBitmap();
}
setIsEmpty(true);
invalidate();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled())
return false;
float eventX = event.getX();
float eventY = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
getParent().requestDisallowInterceptTouchEvent(true);
mPoints.clear();
if (isDoubleClick()) break;
mLastTouchX = eventX;
mLastTouchY = eventY;
addPoint(getNewPoint(eventX, eventY));
if (mOnSignedListener != null) mOnSignedListener.onStartSigning();
case MotionEvent.ACTION_MOVE:
resetDirtyRect(eventX, eventY);
addPoint(getNewPoint(eventX, eventY));
break;
case MotionEvent.ACTION_UP:
resetDirtyRect(eventX, eventY);
addPoint(getNewPoint(eventX, eventY));
getParent().requestDisallowInterceptTouchEvent(true);
setIsEmpty(false);
break;
default:
return false;
}
//invalidate();
invalidate(
(int) (mDirtyRect.left - mMaxWidth),
(int) (mDirtyRect.top - mMaxWidth),
(int) (mDirtyRect.right + mMaxWidth),
(int) (mDirtyRect.bottom + mMaxWidth));
return true;
}
@Override
protected void onDraw(Canvas canvas) {
if (mSignatureBitmap != null) {
canvas.drawBitmap(mSignatureBitmap, 0, 0, mPaint);
}
}
public void setOnSignedListener(OnSignedListener listener) {
mOnSignedListener = listener;
}
public boolean isEmpty() {
return mIsEmpty;
}
public String getSignatureSvg() {
int width = getTransparentSignatureBitmap().getWidth();
int height = getTransparentSignatureBitmap().getHeight();
return mSvgBuilder.build(width, height);
}
public Bitmap getSignatureBitmap() {
Bitmap originalBitmap = getTransparentSignatureBitmap();
Bitmap whiteBgBitmap = Bitmap.createBitmap(originalBitmap.getWidth(), originalBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(whiteBgBitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(originalBitmap, 0, 0, null);
return whiteBgBitmap;
}
/**
* @param compressPercentage Hint to the compressor, 0-100 percent. 0 meaning compress for
* small size, 100 meaning compress for max quality. Some
* formats, like PNG which is lossless, will ignore the
* quality setting
*/
public Bitmap getCompressedSignatureBitmap(int compressPercentage) {
if (compressPercentage < 0) {
compressPercentage = 0;
} else if (compressPercentage > 100) {
compressPercentage = 100;
}
Bitmap originalBitmap = getTransparentSignatureBitmap();
int originalWidth = originalBitmap.getWidth();
int originalHeight = originalBitmap.getHeight();
int targetWidth = originalWidth * compressPercentage / 100; // your arbitrary fixed limit
int targetHeight = (int) (originalHeight * targetWidth / (double) originalWidth);
Bitmap whiteBgBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(whiteBgBitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(originalBitmap, 0, 0, null);
whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true);
return whiteBgBitmap;
}
/**
* @param deiredWidth Desired width of the bitmap
*/
public Bitmap getFixedSizeSignatureBitmap(int deiredWidth) {
Bitmap originalBitmap = getTransparentSignatureBitmap();
int originalWidth = originalBitmap.getWidth();
int originalHeight = originalBitmap.getHeight();
int targetWidth = deiredWidth; // your arbitrary fixed limit
int targetHeight = (int) (originalHeight * targetWidth / (double) originalWidth);
Bitmap whiteBgBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(whiteBgBitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(originalBitmap, 0, 0, null);
whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true);
return whiteBgBitmap;
}
/**
* @param deiredWidth Desired width of the bitmap
*/
public Bitmap getFixedSizeSignatureBitmap(int deiredWidth,int desiredHeight) {
Bitmap originalBitmap = getTransparentSignatureBitmap();
int originalWidth = originalBitmap.getWidth();
int originalHeight = originalBitmap.getHeight();
int targetWidth = deiredWidth; // your arbitrary fixed limit
int targetHeight = desiredHeight;
Bitmap whiteBgBitmap = Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(whiteBgBitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(originalBitmap, 0, 0, null);
whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true);
return whiteBgBitmap;
}
public void setSignatureBitmap(final Bitmap signature) {
// View was laid out...
if (ViewCompat.isLaidOut(this)) {
clear();
ensureSignatureBitmap();
RectF tempSrc = new RectF();
RectF tempDst = new RectF();
int dWidth = signature.getWidth();
int dHeight = signature.getHeight();
int vWidth = getWidth();
int vHeight = getHeight();
// Generate the required transform.
tempSrc.set(0, 0, dWidth, dHeight);
tempDst.set(0, 0, vWidth, vHeight);
Matrix drawMatrix = new Matrix();
drawMatrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.CENTER);
Canvas canvas = new Canvas(mSignatureBitmap);
canvas.drawBitmap(signature, drawMatrix, null);
setIsEmpty(false);
invalidate();
}
// View not laid out yet e.g. called from onCreate(), onRestoreInstanceState()...
else {
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// Remove layout listener...
ViewTreeObserverCompat.removeOnGlobalLayoutListener(getViewTreeObserver(), this);
// Signature bitmap...
setSignatureBitmap(signature);
}
});
}
}
public Bitmap getTransparentSignatureBitmap() {
ensureSignatureBitmap();
return mSignatureBitmap;
}
public Bitmap getTransparentSignatureBitmap(boolean trimBlankSpace) {
if (!trimBlankSpace) {
return getTransparentSignatureBitmap();
}
ensureSignatureBitmap();
int imgHeight = mSignatureBitmap.getHeight();
int imgWidth = mSignatureBitmap.getWidth();
int backgroundColor = Color.TRANSPARENT;
int xMin = Integer.MAX_VALUE,
xMax = Integer.MIN_VALUE,
yMin = Integer.MAX_VALUE,
yMax = Integer.MIN_VALUE;
boolean foundPixel = false;
// Find xMin
for (int x = 0; x < imgWidth; x++) {
boolean stop = false;
for (int y = 0; y < imgHeight; y++) {
if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
xMin = x;
stop = true;
foundPixel = true;
break;
}
}
if (stop)
break;
}
// Image is empty...
if (!foundPixel)
return null;
// Find yMin
for (int y = 0; y < imgHeight; y++) {
boolean stop = false;
for (int x = xMin; x < imgWidth; x++) {
if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
yMin = y;
stop = true;
break;
}
}
if (stop)
break;
}
// Find xMax
for (int x = imgWidth - 1; x >= xMin; x--) {
boolean stop = false;
for (int y = yMin; y < imgHeight; y++) {
if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
xMax = x;
stop = true;
break;
}
}
if (stop)
break;
}
// Find yMax
for (int y = imgHeight - 1; y >= yMin; y--) {
boolean stop = false;
for (int x = xMin; x <= xMax; x++) {
if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
yMax = y;
stop = true;
break;
}
}
if (stop)
break;
}
return Bitmap.createBitmap(mSignatureBitmap, xMin, yMin, xMax - xMin, yMax - yMin);
}
private boolean isDoubleClick() {
if (mClearOnDoubleClick) {
if (mFirstClick != 0 && System.currentTimeMillis() - mFirstClick > DOUBLE_CLICK_DELAY_MS) {
mCountClick = 0;
}
mCountClick++;
if (mCountClick == 1) {
mFirstClick = System.currentTimeMillis();
} else if (mCountClick == 2) {
long lastClick = System.currentTimeMillis();
if (lastClick - mFirstClick < DOUBLE_CLICK_DELAY_MS) {
this.clear();
return true;
}
}
}
return false;
}
private TimedPoint getNewPoint(float x, float y) {
int mCacheSize = mPointsCache.size();
TimedPoint timedPoint;
if (mCacheSize == 0) {
// Cache is empty, create a new point
timedPoint = new TimedPoint();
} else {
// Get point from cache
timedPoint = mPointsCache.remove(mCacheSize - 1);
}
return timedPoint.set(x, y);
}
private void recyclePoint(TimedPoint point) {
mPointsCache.add(point);
}
private void addPoint(TimedPoint newPoint) {
mPoints.add(newPoint);
int pointsCount = mPoints.size();
if (pointsCount > 3) {
ControlTimedPoints tmp = calculateCurveControlPoints(mPoints.get(0), mPoints.get(1), mPoints.get(2));
TimedPoint c2 = tmp.c2;
recyclePoint(tmp.c1);
tmp = calculateCurveControlPoints(mPoints.get(1), mPoints.get(2), mPoints.get(3));
TimedPoint c3 = tmp.c1;
recyclePoint(tmp.c2);
CurveBezier curve = mCurveBezierCached.set(mPoints.get(1), c2, c3, mPoints.get(2));
TimedPoint startPoint = curve.startPoint;
TimedPoint endPoint = curve.endPoint;
float velocity = endPoint.velocityFrom(startPoint);
velocity = Float.isNaN(velocity) ? 0.0f : velocity;
velocity = mVelocityFilterWeight * velocity
+ (1 - mVelocityFilterWeight) * mLastVelocity;
// The new width is a function of the velocity. Higher velocities
// correspond to thinner strokes.
float newWidth = strokeWidth(velocity);
// The Bezier's width starts out as last curve's final width, and
// gradually changes to the stroke width just calculated. The new
// width calculation is based on the velocity between the Bezier's
// start and end mPoints.
addBezier(curve, mLastWidth, newWidth);
mLastVelocity = velocity;
mLastWidth = newWidth;
// Remove the first element from the list,
// so that we always have no more than 4 mPoints in mPoints array.
recyclePoint(mPoints.remove(0));
recyclePoint(c2);
recyclePoint(c3);
} else if (pointsCount == 1) {
// To reduce the initial lag make it work with 3 mPoints
// by duplicating the first point
TimedPoint firstPoint = mPoints.get(0);
mPoints.add(getNewPoint(firstPoint.x, firstPoint.y));
}
}
private void addBezier(CurveBezier curve, float startWidth, float endWidth) {
mSvgBuilder.append(curve, (startWidth + endWidth) / 2);
ensureSignatureBitmap();
float originalWidth = mPaint.getStrokeWidth();
float widthDelta = endWidth - startWidth;
float drawSteps = (float) Math.floor(curve.length());
for (int i = 0; i < drawSteps; i++) {
// Calculate the Bezier (x, y) coordinate for this step.
float t = ((float) i) / drawSteps;
float tt = t * t;
float ttt = tt * t;
float u = 1 - t;
float uu = u * u;
float uuu = uu * u;
float x = uuu * curve.startPoint.x;
x += 3 * uu * t * curve.control1.x;
x += 3 * u * tt * curve.control2.x;
x += ttt * curve.endPoint.x;
float y = uuu * curve.startPoint.y;
y += 3 * uu * t * curve.control1.y;
y += 3 * u * tt * curve.control2.y;
y += ttt * curve.endPoint.y;
// Set the incremental stroke width and draw.
mPaint.setStrokeWidth(startWidth + ttt * widthDelta);
mSignatureBitmapCanvas.drawPoint(x, y, mPaint);
expandDirtyRect(x, y);
}
mPaint.setStrokeWidth(originalWidth);
}
private ControlTimedPoints calculateCurveControlPoints(TimedPoint s1, TimedPoint s2, TimedPoint s3) {
float dx1 = s1.x - s2.x;
float dy1 = s1.y - s2.y;
float dx2 = s2.x - s3.x;
float dy2 = s2.y - s3.y;
float m1X = (s1.x + s2.x) / 2.0f;
float m1Y = (s1.y + s2.y) / 2.0f;
float m2X = (s2.x + s3.x) / 2.0f;
float m2Y = (s2.y + s3.y) / 2.0f;
float l1 = (float) Math.sqrt(dx1 * dx1 + dy1 * dy1);
float l2 = (float) Math.sqrt(dx2 * dx2 + dy2 * dy2);
float dxm = (m1X - m2X);
float dym = (m1Y - m2Y);
float k = l2 / (l1 + l2);
if (Float.isNaN(k)) k = 0.0f;
float cmX = m2X + dxm * k;
float cmY = m2Y + dym * k;
float tx = s2.x - cmX;
float ty = s2.y - cmY;
return mControlTimedPointsCached.set(getNewPoint(m1X + tx, m1Y + ty), getNewPoint(m2X + tx, m2Y + ty));
}
private float strokeWidth(float velocity) {
return Math.max(mMaxWidth / (velocity + 1), mMinWidth);
}
/**
* Called when replaying history to ensure the dirty region includes all
* mPoints.
*
* @param historicalX the previous x coordinate.
* @param historicalY the previous y coordinate.
*/
private void expandDirtyRect(float historicalX, float historicalY) {
if (historicalX < mDirtyRect.left) {
mDirtyRect.left = historicalX;
} else if (historicalX > mDirtyRect.right) {
mDirtyRect.right = historicalX;
}
if (historicalY < mDirtyRect.top) {
mDirtyRect.top = historicalY;
} else if (historicalY > mDirtyRect.bottom) {
mDirtyRect.bottom = historicalY;
}
}
/**
* Resets the dirty region when the motion event occurs.
*
* @param eventX the event x coordinate.
* @param eventY the event y coordinate.
*/
private void resetDirtyRect(float eventX, float eventY) {
// The mLastTouchX and mLastTouchY were set when the ACTION_DOWN motion event occurred.
mDirtyRect.left = Math.min(mLastTouchX, eventX);
mDirtyRect.right = Math.max(mLastTouchX, eventX);
mDirtyRect.top = Math.min(mLastTouchY, eventY);
mDirtyRect.bottom = Math.max(mLastTouchY, eventY);
}
private void setIsEmpty(boolean newValue) {
mIsEmpty = newValue;
if (mOnSignedListener != null) {
if (mIsEmpty) {
mOnSignedListener.onClear();
} else {
mOnSignedListener.onSigned();
}
}
}
private void ensureSignatureBitmap() {
if (mSignatureBitmap == null) {
mSignatureBitmap = Bitmap.createBitmap(getWidth(), getHeight(),
Bitmap.Config.ARGB_8888);
mSignatureBitmapCanvas = new Canvas(mSignatureBitmap);
}
}
private int convertDpToPx(float dp) {
return Math.round(getContext().getResources().getDisplayMetrics().density * dp);
}
}

View File

@@ -0,0 +1,567 @@
package com.navi.amc.investorapp.signature.ui
import android.content.Context
import android.content.res.Resources
import android.graphics.*
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import com.navi.amc.investorapp.R
import com.navi.amc.investorapp.signature.drawerControllers.TimedPoint
import com.navi.amc.investorapp.signature.svgUtils.SvgBuilder
import com.navi.amc.investorapp.signature.drawerControllers.ControlTimedPoints
import com.navi.amc.investorapp.signature.drawerControllers.CurveBezier
import java.util.ArrayList
class SilkySignaturePad(context: Context, attrs: AttributeSet?) : View(context, attrs) {
//View state
private var mPoints: MutableList<TimedPoint>? = null
private var mIsEmpty = false
private var mLastTouchX = 0f
private var mLastTouchY = 0f
private var mLastVelocity = 0f
private var mLastWidth = 0f
private val mDirtyRect: RectF
private val mSvgBuilder = SvgBuilder()
// Cache
private val mPointsCache: MutableList<TimedPoint?> = ArrayList()
private val mControlTimedPointsCached = ControlTimedPoints()
private val mCurveBezierCached = CurveBezier()
//Configurable parameters
private var mMinWidth = 0
private var mMaxWidth = 0
private var mVelocityFilterWeight = 0f
private var mOnSignedListener: OnSignedListener? = null
private var mClearOnDoubleClick = false
//Click values
private var mFirstClick: Long = 0
private var mCountClick = 0
//Default attribute values
private val DEFAULT_ATTR_PEN_MIN_WIDTH_PX = 3
private val DEFAULT_ATTR_PEN_MAX_WIDTH_PX = 7
private val DEFAULT_ATTR_PEN_COLOR = Color.BLACK
private val DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT = 0.9f
private val DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK = false
private val mPaint = Paint()
private var mSignatureBitmap: Bitmap? = null
private var mSignatureBitmapCanvas: Canvas? = null
/**
* Set the pen color from a given resource.
* If the resource is not found, [Color.BLACK] is assumed.
*
* @param colorRes the color resource.
*/
fun setPenColorRes(colorRes: Int) {
try {
setPenColor(resources.getColor(colorRes))
} catch (ex: Resources.NotFoundException) {
setPenColor(Color.parseColor("#000000"))
}
}
/**
* Set the pen color from a given color.
*
* @param color the color.
*/
fun setPenColor(color: Int) {
mPaint.color = color
}
/**
* Set the minimum width of the stroke in pixel.
*
* @param minWidth the width in dp.
*/
fun setMinWidth(minWidth: Float) {
mMinWidth = convertDpToPx(minWidth)
}
/**
* Set the maximum width of the stroke in pixel.
*
* @param maxWidth the width in dp.
*/
fun setMaxWidth(maxWidth: Float) {
mMaxWidth = convertDpToPx(maxWidth)
}
/**
* Set the velocity filter weight.
*
* @param velocityFilterWeight the weight.
*/
fun setVelocityFilterWeight(velocityFilterWeight: Float) {
mVelocityFilterWeight = velocityFilterWeight
}
fun clear() {
mSvgBuilder.clear()
mPoints = ArrayList()
mLastVelocity = 0f
mLastWidth = ((mMinWidth + mMaxWidth) / 2).toFloat()
if (mSignatureBitmap != null) {
mSignatureBitmap = null
ensureSignatureBitmap()
}
isEmpty = true
invalidate()
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (!isEnabled) return false
val eventX = event.x
val eventY = event.y
when (event.action) {
MotionEvent.ACTION_DOWN -> {
parent.requestDisallowInterceptTouchEvent(true)
mPoints!!.clear()
if (isDoubleClick) return false
mLastTouchX = eventX
mLastTouchY = eventY
addPoint(getNewPoint(eventX, eventY))
if (mOnSignedListener != null) mOnSignedListener!!.onStartSigning()
resetDirtyRect(eventX, eventY)
addPoint(getNewPoint(eventX, eventY))
}
MotionEvent.ACTION_MOVE -> {
resetDirtyRect(eventX, eventY)
addPoint(getNewPoint(eventX, eventY))
}
MotionEvent.ACTION_UP -> {
resetDirtyRect(eventX, eventY)
addPoint(getNewPoint(eventX, eventY))
parent.requestDisallowInterceptTouchEvent(true)
isEmpty = false
}
else -> return false
}
//invalidate();
invalidate(
(mDirtyRect.left - mMaxWidth).toInt(),
(mDirtyRect.top - mMaxWidth).toInt(),
(mDirtyRect.right + mMaxWidth).toInt(),
(mDirtyRect.bottom + mMaxWidth).toInt()
)
return true
}
override fun onDraw(canvas: Canvas) {
if (mSignatureBitmap != null) {
canvas.drawBitmap(mSignatureBitmap!!, 0f, 0f, mPaint)
}
}
fun setOnSignedListener(listener: OnSignedListener?) {
mOnSignedListener = listener
}
var isEmpty: Boolean
get() = mIsEmpty
private set(newValue) {
mIsEmpty = newValue
if (mOnSignedListener != null) {
if (mIsEmpty) {
mOnSignedListener!!.onClear()
} else {
mOnSignedListener!!.onSigned()
}
}
}
val signatureSvg: String
get() {
val width = transparentSignatureBitmap!!.width
val height = transparentSignatureBitmap!!.height
return mSvgBuilder.build(width, height)
}
val signatureBitmap: Bitmap
get() {
val originalBitmap = transparentSignatureBitmap
val whiteBgBitmap = Bitmap.createBitmap(
originalBitmap!!.width, originalBitmap.height, Bitmap.Config.ARGB_8888
)
val canvas = Canvas(whiteBgBitmap)
canvas.drawColor(Color.WHITE)
canvas.drawBitmap(originalBitmap, 0f, 0f, null)
return whiteBgBitmap
}
/**
* @param compressPercentage Hint to the compressor, 0-100 percent. 0 meaning compress for
* small size, 100 meaning compress for max quality. Some
* formats, like PNG which is lossless, will ignore the
* quality setting
*/
fun getCompressedSignatureBitmap(compressPercentage: Int): Bitmap {
var compressPercentage = compressPercentage
if (compressPercentage < 0) {
compressPercentage = 0
} else if (compressPercentage > 100) {
compressPercentage = 100
}
val originalBitmap = transparentSignatureBitmap
val originalWidth = originalBitmap!!.width
val originalHeight = originalBitmap.height
val targetWidth = originalWidth * compressPercentage / 100 // your arbitrary fixed limit
val targetHeight = (originalHeight * targetWidth / originalWidth.toDouble()).toInt()
var whiteBgBitmap =
Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(whiteBgBitmap)
canvas.drawColor(Color.WHITE)
canvas.drawBitmap(originalBitmap, 0f, 0f, null)
whiteBgBitmap = Bitmap.createScaledBitmap(originalBitmap, targetWidth, targetHeight, true)
return whiteBgBitmap
}
/**
* @param deiredWidth Desired width of the bitmap
*/
fun getFixedSizeSignatureBitmap(deiredWidth: Int): Bitmap {
val originalBitmap = transparentSignatureBitmap
val originalWidth = originalBitmap!!.width
val originalHeight = originalBitmap.height
val targetHeight = (originalHeight * deiredWidth // your arbitrary fixed limit
/ originalWidth.toDouble()).toInt()
var whiteBgBitmap =
Bitmap.createBitmap(originalWidth, originalHeight, Bitmap.Config.ARGB_8888)
val canvas = Canvas(whiteBgBitmap)
canvas.drawColor(Color.WHITE)
canvas.drawBitmap(originalBitmap, 0f, 0f, null)
whiteBgBitmap = Bitmap.createScaledBitmap(
originalBitmap, deiredWidth, targetHeight, true
)
return whiteBgBitmap
}
val transparentSignatureBitmap: Bitmap?
get() {
ensureSignatureBitmap()
return mSignatureBitmap
}
fun getTransparentSignatureBitmap(trimBlankSpace: Boolean): Bitmap? {
if (!trimBlankSpace) {
return transparentSignatureBitmap
}
ensureSignatureBitmap()
val imgHeight = mSignatureBitmap!!.height
val imgWidth = mSignatureBitmap!!.width
val backgroundColor = Color.TRANSPARENT
var xMin = Int.MAX_VALUE
var xMax = Int.MIN_VALUE
var yMin = Int.MAX_VALUE
var yMax = Int.MIN_VALUE
var foundPixel = false
// Find xMin
for (x in 0 until imgWidth) {
var stop = false
for (y in 0 until imgHeight) {
if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) {
xMin = x
stop = true
foundPixel = true
break
}
}
if (stop) break
}
// Image is empty...
if (!foundPixel) return null
// Find yMin
for (y in 0 until imgHeight) {
var stop = false
for (x in xMin until imgWidth) {
if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) {
yMin = y
stop = true
break
}
}
if (stop) break
}
// Find xMax
for (x in imgWidth - 1 downTo xMin) {
var stop = false
for (y in yMin until imgHeight) {
if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) {
xMax = x
stop = true
break
}
}
if (stop) break
}
// Find yMax
for (y in imgHeight - 1 downTo yMin) {
var stop = false
for (x in xMin..xMax) {
if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) {
yMax = y
stop = true
break
}
}
if (stop) break
}
return Bitmap.createBitmap(mSignatureBitmap!!, xMin, yMin, xMax - xMin, yMax - yMin)
}
private val isDoubleClick: Boolean
private get() {
if (mClearOnDoubleClick) {
if (mFirstClick != 0L && System.currentTimeMillis() - mFirstClick > DOUBLE_CLICK_DELAY_MS) {
mCountClick = 0
}
mCountClick++
if (mCountClick == 1) {
mFirstClick = System.currentTimeMillis()
} else if (mCountClick == 2) {
val lastClick = System.currentTimeMillis()
if (lastClick - mFirstClick < DOUBLE_CLICK_DELAY_MS) {
this.clear()
return true
}
}
}
return false
}
private fun getNewPoint(x: Float, y: Float): TimedPoint {
val mCacheSize = mPointsCache.size
val timedPoint: TimedPoint?
timedPoint = if (mCacheSize == 0) {
// Cache is empty, create a new point
TimedPoint()
} else {
// Get point from cache
mPointsCache.removeAt(mCacheSize - 1)
}
return timedPoint!!.set(x, y)
}
private fun recyclePoint(point: TimedPoint?) {
mPointsCache.add(point)
}
private fun addPoint(newPoint: TimedPoint) {
mPoints!!.add(newPoint)
val pointsCount = mPoints!!.size
if (pointsCount > 3) {
var tmp = calculateCurveControlPoints(mPoints!![0], mPoints!![1], mPoints!![2])
val c2 = tmp.c2
recyclePoint(tmp.c1)
tmp = calculateCurveControlPoints(mPoints!![1], mPoints!![2], mPoints!![3])
val c3 = tmp.c1
recyclePoint(tmp.c2)
val curve = mCurveBezierCached.set(mPoints!![1], c2, c3, mPoints!![2])
val startPoint = curve.startPoint
val endPoint = curve.endPoint
var velocity = endPoint!!.velocityFrom(startPoint)
velocity = if (java.lang.Float.isNaN(velocity)) 0.0f else velocity
velocity = (mVelocityFilterWeight * velocity
+ (1 - mVelocityFilterWeight) * mLastVelocity)
// The new width is a function of the velocity. Higher velocities
// correspond to thinner strokes.
val newWidth = strokeWidth(velocity)
// The Bezier's width starts out as last curve's final width, and
// gradually changes to the stroke width just calculated. The new
// width calculation is based on the velocity between the Bezier's
// start and end mPoints.
addBezier(curve, mLastWidth, newWidth)
mLastVelocity = velocity
mLastWidth = newWidth
// Remove the first element from the list,
// so that we always have no more than 4 mPoints in mPoints array.
recyclePoint(mPoints!!.removeAt(0))
recyclePoint(c2)
recyclePoint(c3)
} else if (pointsCount == 1) {
// To reduce the initial lag make it work with 3 mPoints
// by duplicating the first point
val firstPoint = mPoints!![0]
mPoints!!.add(getNewPoint(firstPoint.x, firstPoint.y))
}
}
private fun addBezier(curve: CurveBezier, startWidth: Float, endWidth: Float) {
mSvgBuilder.append(curve, (startWidth + endWidth) / 2)
ensureSignatureBitmap()
val originalWidth = mPaint.strokeWidth
val widthDelta = endWidth - startWidth
val drawSteps = Math.floor(curve.length().toDouble()).toFloat()
var i = 0
while (i < drawSteps) {
// Calculate the Bezier (x, y) coordinate for this step.
val t = i.toFloat() / drawSteps
val tt = t * t
val ttt = tt * t
val u = 1 - t
val uu = u * u
val uuu = uu * u
var x = uuu * curve.startPoint!!.x
x += 3 * uu * t * curve.control1!!.x
x += 3 * u * tt * curve.control2!!.x
x += ttt * curve.endPoint!!.x
var y = uuu * curve.startPoint!!.y
y += 3 * uu * t * curve.control1!!.y
y += 3 * u * tt * curve.control2!!.y
y += ttt * curve.endPoint!!.y
// Set the incremental stroke width and draw.
mPaint.strokeWidth = startWidth + ttt * widthDelta
mSignatureBitmapCanvas!!.drawPoint(x, y, mPaint)
expandDirtyRect(x, y)
i++
}
mPaint.strokeWidth = originalWidth
}
private fun calculateCurveControlPoints(
s1: TimedPoint,
s2: TimedPoint,
s3: TimedPoint
): ControlTimedPoints {
val dx1 = s1.x - s2.x
val dy1 = s1.y - s2.y
val dx2 = s2.x - s3.x
val dy2 = s2.y - s3.y
val m1X = (s1.x + s2.x) / 2.0f
val m1Y = (s1.y + s2.y) / 2.0f
val m2X = (s2.x + s3.x) / 2.0f
val m2Y = (s2.y + s3.y) / 2.0f
val l1 = Math.sqrt((dx1 * dx1 + dy1 * dy1).toDouble()).toFloat()
val l2 = Math.sqrt((dx2 * dx2 + dy2 * dy2).toDouble()).toFloat()
val dxm = m1X - m2X
val dym = m1Y - m2Y
var k = l2 / (l1 + l2)
if (java.lang.Float.isNaN(k)) k = 0.0f
val cmX = m2X + dxm * k
val cmY = m2Y + dym * k
val tx = s2.x - cmX
val ty = s2.y - cmY
return mControlTimedPointsCached.set(
getNewPoint(m1X + tx, m1Y + ty),
getNewPoint(m2X + tx, m2Y + ty)
)
}
private fun strokeWidth(velocity: Float): Float {
return Math.max(mMaxWidth / (velocity + 1), mMinWidth.toFloat())
}
/**
* Called when replaying history to ensure the dirty region includes all
* mPoints.
*
* @param historicalX the previous x coordinate.
* @param historicalY the previous y coordinate.
*/
private fun expandDirtyRect(historicalX: Float, historicalY: Float) {
if (historicalX < mDirtyRect.left) {
mDirtyRect.left = historicalX
} else if (historicalX > mDirtyRect.right) {
mDirtyRect.right = historicalX
}
if (historicalY < mDirtyRect.top) {
mDirtyRect.top = historicalY
} else if (historicalY > mDirtyRect.bottom) {
mDirtyRect.bottom = historicalY
}
}
/**
* Resets the dirty region when the motion event occurs.
*
* @param eventX the event x coordinate.
* @param eventY the event y coordinate.
*/
private fun resetDirtyRect(eventX: Float, eventY: Float) {
// The mLastTouchX and mLastTouchY were set when the ACTION_DOWN motion event occurred.
mDirtyRect.left = Math.min(mLastTouchX, eventX)
mDirtyRect.right = Math.max(mLastTouchX, eventX)
mDirtyRect.top = Math.min(mLastTouchY, eventY)
mDirtyRect.bottom = Math.max(mLastTouchY, eventY)
}
private fun ensureSignatureBitmap() {
if (mSignatureBitmap == null) {
mSignatureBitmap = Bitmap.createBitmap(
width, height,
Bitmap.Config.ARGB_8888
)
mSignatureBitmap?.let {
mSignatureBitmapCanvas = Canvas(it)
}
}
}
private fun convertDpToPx(dp: Float): Int {
return Math.round(context.resources.displayMetrics.density * dp)
}
companion object {
private const val DOUBLE_CLICK_DELAY_MS = 200
}
init {
val a = context.theme.obtainStyledAttributes(
attrs,
R.styleable.SilkySignaturePad,
0, 0
)
//Configurable parameters
try {
mMinWidth = a.getDimensionPixelSize(
R.styleable.SilkySignaturePad_penMinWidth,
convertDpToPx(DEFAULT_ATTR_PEN_MIN_WIDTH_PX.toFloat())
)
mMaxWidth = a.getDimensionPixelSize(
R.styleable.SilkySignaturePad_penMaxWidth,
convertDpToPx(DEFAULT_ATTR_PEN_MAX_WIDTH_PX.toFloat())
)
mPaint.color = a.getColor(
R.styleable.SilkySignaturePad_penColor,
DEFAULT_ATTR_PEN_COLOR
)
mVelocityFilterWeight = a.getFloat(
R.styleable.SilkySignaturePad_velocityFilterWeight,
DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT
)
mClearOnDoubleClick = a.getBoolean(
R.styleable.SilkySignaturePad_clearOnDoubleClick,
DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK
)
} finally {
a.recycle()
}
//Fixed parameters
mPaint.isAntiAlias = true
mPaint.style = Paint.Style.STROKE
mPaint.strokeCap = Paint.Cap.ROUND
mPaint.strokeJoin = Paint.Join.ROUND
//Dirty rectangle to update only the changed portion of the view
mDirtyRect = RectF()
clear()
}
}

View File

@@ -1,15 +0,0 @@
package com.navi.amc.investorapp.signature.viewHelper;
import android.os.Build;
import android.view.View;
public class ViewCompat {
public static boolean isLaidOut(View view) {
// Future (API19+)...
if (Build.VERSION.SDK_INT >= 19) {
return view.isLaidOut();
}
// Legacy...
return view.getWidth() > 0 && view.getHeight() > 0;
}
}

View File

@@ -0,0 +1,14 @@
package com.navi.amc.investorapp.signature.viewHelper
import android.os.Build
import android.view.View
object ViewCompat {
fun isLaidOut(view: View): Boolean {
// Future (API19+)...
return if (Build.VERSION.SDK_INT >= 19) {
view.isLaidOut
} else view.width > 0 && view.height > 0
// Legacy...
}
}

View File

@@ -1,20 +0,0 @@
package com.navi.amc.investorapp.signature.viewHelper;
import android.annotation.SuppressLint;
import android.os.Build;
import android.view.ViewTreeObserver;
public class ViewTreeObserverCompat {
@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
public static void removeOnGlobalLayoutListener(ViewTreeObserver observer, ViewTreeObserver.OnGlobalLayoutListener victim) {
// Future (API16+)...
if (Build.VERSION.SDK_INT >= 16) {
observer.removeOnGlobalLayoutListener(victim);
}
// Legacy...
else {
observer.removeGlobalOnLayoutListener(victim);
}
}
}

View File

@@ -0,0 +1,20 @@
package com.navi.amc.investorapp.signature.viewHelper
import android.annotation.SuppressLint
import android.os.Build
import android.view.ViewTreeObserver
object ViewTreeObserverCompat {
@SuppressLint("NewApi")
fun removeOnGlobalLayoutListener(
observer: ViewTreeObserver,
victim: ViewTreeObserver.OnGlobalLayoutListener?
) {
// Future (API16+)...
if (Build.VERSION.SDK_INT >= 16) {
observer.removeOnGlobalLayoutListener(victim)
} else {
observer.removeGlobalOnLayoutListener(victim)
}
}
}