android 电子签名 手写签名 功能实现

举报
再见孙悟空_ 发表于 2022/01/12 22:26:12 2022/01/12
【摘要】 android 电子签名  手写签名 功能实现 这个手写的效果 就是一个 重写的的自定义的view  代码如下: package com.example.hand.views; import java.util.ArrayList;import java.util.List; import a...

android 电子签名  手写签名 功能实现

这个手写的效果 就是一个 重写的的自定义的view  代码如下:


  
  1. package com.example.hand.views;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import android.content.Context;
  5. import android.content.res.Resources;
  6. import android.content.res.TypedArray;
  7. import android.graphics.Bitmap;
  8. import android.graphics.Canvas;
  9. import android.graphics.Color;
  10. import android.graphics.Matrix;
  11. import android.graphics.Paint;
  12. import android.graphics.Path;
  13. import android.graphics.RectF;
  14. import android.util.AttributeSet;
  15. import android.util.DisplayMetrics;
  16. import android.view.MotionEvent;
  17. import android.view.View;
  18. import com.example.hand.R;
  19. import com.example.hand.utils.Bezier;
  20. import com.example.hand.utils.ControlTimedPoints;
  21. import com.example.hand.utils.TimedPoint;
  22. public class SignatureView extends View {
  23. // View state
  24. private List<TimedPoint> mPoints;
  25. private boolean mIsEmpty;
  26. private float mLastTouchX;
  27. private float mLastTouchY;
  28. private float mLastVelocity;
  29. private float mLastWidth;
  30. private RectF mDirtyRect;
  31. // Configurable parameters
  32. private int mMinWidth;
  33. private int mMaxWidth;
  34. private float mVelocityFilterWeight;
  35. private OnSignedListener mOnSignedListener;
  36. private Paint mPaint = new Paint();
  37. private Path mPath = new Path();
  38. private Bitmap mSignatureBitmap = null;
  39. private Canvas mSignatureBitmapCanvas = null;
  40. public SignatureView(Context context, AttributeSet attrs) {
  41. super(context, attrs);
  42. TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.SignatureView, 0, 0);
  43. // Configurable parameters
  44. try {
  45. mMinWidth = a.getDimensionPixelSize(R.styleable.SignatureView_minWidth, convertDpToPx(3));
  46. mMaxWidth = a.getDimensionPixelSize(R.styleable.SignatureView_maxWidth, convertDpToPx(7));
  47. mVelocityFilterWeight = a.getFloat(R.styleable.SignatureView_velocityFilterWeight, 0.9f);
  48. mPaint.setColor(a.getColor(R.styleable.SignatureView_penColor, Color.BLACK));
  49. } finally {
  50. a.recycle();
  51. }
  52. // Fixed parameters
  53. mPaint.setAntiAlias(true);
  54. mPaint.setStyle(Paint.Style.STROKE);
  55. mPaint.setStrokeCap(Paint.Cap.ROUND);
  56. mPaint.setStrokeJoin(Paint.Join.ROUND);
  57. // Dirty rectangle to update only the changed portion of the view
  58. mDirtyRect = new RectF();
  59. clear();
  60. }
  61. /**
  62. * Set the pen color from a given resource. If the resource is not found,
  63. * {@link android.graphics.Color#BLACK} is assumed.
  64. *
  65. * @param colorRes
  66. * the color resource.
  67. */
  68. public void setPenColorRes(int colorRes) {
  69. try {
  70. setPenColor(getResources().getColor(colorRes));
  71. } catch (Resources.NotFoundException ex) {
  72. setPenColor(getResources().getColor(Color.BLACK));
  73. }
  74. }
  75. /**
  76. * Set the pen color from a given color.
  77. *
  78. * @param color
  79. * the color.
  80. */
  81. public void setPenColor(int color) {
  82. mPaint.setColor(color);
  83. }
  84. /**
  85. * Set the minimum width of the stroke in pixel.
  86. *
  87. * @param minWidth
  88. * the width in dp.
  89. */
  90. public void setMinWidth(float minWidth) {
  91. mMinWidth = convertDpToPx(minWidth);
  92. }
  93. /**
  94. * Set the maximum width of the stroke in pixel.
  95. *
  96. * @param maxWidth
  97. * the width in dp.
  98. */
  99. public void setMaxWidth(float maxWidth) {
  100. mMaxWidth = convertDpToPx(maxWidth);
  101. }
  102. /**
  103. * Set the velocity filter weight.
  104. *
  105. * @param velocityFilterWeight
  106. * the weight.
  107. */
  108. public void setVelocityFilterWeight(float velocityFilterWeight) {
  109. mVelocityFilterWeight = velocityFilterWeight;
  110. }
  111. public void clear() {
  112. mPoints = new ArrayList<TimedPoint>();
  113. mLastVelocity = 0;
  114. mLastWidth = (mMinWidth + mMaxWidth) / 2;
  115. mPath.reset();
  116. if (mSignatureBitmap != null) {
  117. mSignatureBitmap = null;
  118. ensureSignatureBitmap();
  119. }
  120. setIsEmpty(true);
  121. invalidate();
  122. }
  123. @Override
  124. public boolean onTouchEvent(MotionEvent event) {
  125. if (!isEnabled())
  126. return false;
  127. float eventX = event.getX();
  128. float eventY = event.getY();
  129. switch (event.getAction()) {
  130. case MotionEvent.ACTION_DOWN:
  131. getParent().requestDisallowInterceptTouchEvent(true);
  132. mPoints.clear();
  133. mPath.moveTo(eventX, eventY);
  134. mLastTouchX = eventX;
  135. mLastTouchY = eventY;
  136. addPoint(new TimedPoint(eventX, eventY));
  137. case MotionEvent.ACTION_MOVE:
  138. resetDirtyRect(eventX, eventY);
  139. addPoint(new TimedPoint(eventX, eventY));
  140. break;
  141. case MotionEvent.ACTION_UP:
  142. resetDirtyRect(eventX, eventY);
  143. addPoint(new TimedPoint(eventX, eventY));
  144. getParent().requestDisallowInterceptTouchEvent(true);
  145. setIsEmpty(false);
  146. break;
  147. default:
  148. return false;
  149. }
  150. // invalidate();
  151. invalidate((int) (mDirtyRect.left - mMaxWidth), (int) (mDirtyRect.top - mMaxWidth),
  152. (int) (mDirtyRect.right + mMaxWidth), (int) (mDirtyRect.bottom + mMaxWidth));
  153. return true;
  154. }
  155. @Override
  156. protected void onDraw(Canvas canvas) {
  157. if (mSignatureBitmap != null) {
  158. canvas.drawBitmap(mSignatureBitmap, 0, 0, mPaint);
  159. }
  160. }
  161. public void setOnSignedListener(OnSignedListener listener) {
  162. mOnSignedListener = listener;
  163. }
  164. public boolean isEmpty() {
  165. return mIsEmpty;
  166. }
  167. public Bitmap getSignatureBitmap() {
  168. Bitmap originalBitmap = getTransparentSignatureBitmap();
  169. Bitmap whiteBgBitmap = Bitmap.createBitmap(originalBitmap.getWidth(), originalBitmap.getHeight(),
  170. Bitmap.Config.ARGB_8888);
  171. Canvas canvas = new Canvas(whiteBgBitmap);
  172. canvas.drawColor(Color.WHITE);
  173. canvas.drawBitmap(originalBitmap, 0, 0, null);
  174. return whiteBgBitmap;
  175. }
  176. public void setSignatureBitmap(Bitmap signature) {
  177. clear();
  178. ensureSignatureBitmap();
  179. RectF tempSrc = new RectF();
  180. RectF tempDst = new RectF();
  181. int dWidth = signature.getWidth();
  182. int dHeight = signature.getHeight();
  183. int vWidth = getWidth();
  184. int vHeight = getHeight();
  185. // Generate the required transform.
  186. tempSrc.set(0, 0, dWidth, dHeight);
  187. tempDst.set(0, 0, vWidth, vHeight);
  188. Matrix drawMatrix = new Matrix();
  189. drawMatrix.setRectToRect(tempSrc, tempDst, Matrix.ScaleToFit.CENTER);
  190. Canvas canvas = new Canvas(mSignatureBitmap);
  191. canvas.drawBitmap(signature, drawMatrix, null);
  192. setIsEmpty(false);
  193. invalidate();
  194. }
  195. public Bitmap getTransparentSignatureBitmap() {
  196. ensureSignatureBitmap();
  197. return mSignatureBitmap;
  198. }
  199. public Bitmap getTransparentSignatureBitmap(boolean trimBlankSpace) {
  200. if (!trimBlankSpace) {
  201. return getTransparentSignatureBitmap();
  202. }
  203. ensureSignatureBitmap();
  204. int imgHeight = mSignatureBitmap.getHeight();
  205. int imgWidth = mSignatureBitmap.getWidth();
  206. int backgroundColor = Color.TRANSPARENT;
  207. int xMin = Integer.MAX_VALUE, xMax = Integer.MIN_VALUE, yMin = Integer.MAX_VALUE, yMax = Integer.MIN_VALUE;
  208. boolean foundPixel = false;
  209. // Find xMin
  210. for (int x = 0; x < imgWidth; x++) {
  211. boolean stop = false;
  212. for (int y = 0; y < imgHeight; y++) {
  213. if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
  214. xMin = x;
  215. stop = true;
  216. foundPixel = true;
  217. break;
  218. }
  219. }
  220. if (stop)
  221. break;
  222. }
  223. // Image is empty...
  224. if (!foundPixel)
  225. return null;
  226. // Find yMin
  227. for (int y = 0; y < imgHeight; y++) {
  228. boolean stop = false;
  229. for (int x = xMin; x < imgWidth; x++) {
  230. if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
  231. yMin = y;
  232. stop = true;
  233. break;
  234. }
  235. }
  236. if (stop)
  237. break;
  238. }
  239. // Find xMax
  240. for (int x = imgWidth - 1; x >= xMin; x--) {
  241. boolean stop = false;
  242. for (int y = yMin; y < imgHeight; y++) {
  243. if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
  244. xMax = x;
  245. stop = true;
  246. break;
  247. }
  248. }
  249. if (stop)
  250. break;
  251. }
  252. // Find yMax
  253. for (int y = imgHeight - 1; y >= yMin; y--) {
  254. boolean stop = false;
  255. for (int x = xMin; x <= xMax; x++) {
  256. if (mSignatureBitmap.getPixel(x, y) != backgroundColor) {
  257. yMax = y;
  258. stop = true;
  259. break;
  260. }
  261. }
  262. if (stop)
  263. break;
  264. }
  265. return Bitmap.createBitmap(mSignatureBitmap, xMin, yMin, xMax - xMin, yMax - yMin);
  266. }
  267. private void addPoint(TimedPoint newPoint) {
  268. mPoints.add(newPoint);
  269. if (mPoints.size() > 2) {
  270. // To reduce the initial lag make it work with 3 mPoints
  271. // by copying the first point to the beginning.
  272. if (mPoints.size() == 3)
  273. mPoints.add(0, mPoints.get(0));
  274. ControlTimedPoints tmp = calculateCurveControlPoints(mPoints.get(0), mPoints.get(1), mPoints.get(2));
  275. TimedPoint c2 = tmp.c2;
  276. tmp = calculateCurveControlPoints(mPoints.get(1), mPoints.get(2), mPoints.get(3));
  277. TimedPoint c3 = tmp.c1;
  278. Bezier curve = new Bezier(mPoints.get(1), c2, c3, mPoints.get(2));
  279. TimedPoint startPoint = curve.startPoint;
  280. TimedPoint endPoint = curve.endPoint;
  281. float velocity = endPoint.velocityFrom(startPoint);
  282. velocity = Float.isNaN(velocity) ? 0.0f : velocity;
  283. velocity = mVelocityFilterWeight * velocity + (1 - mVelocityFilterWeight) * mLastVelocity;
  284. // The new width is a function of the velocity. Higher velocities
  285. // correspond to thinner strokes.
  286. float newWidth = strokeWidth(velocity);
  287. // The Bezier's width starts out as last curve's final width, and
  288. // gradually changes to the stroke width just calculated. The new
  289. // width calculation is based on the velocity between the Bezier's
  290. // start and end mPoints.
  291. addBezier(curve, mLastWidth, newWidth);
  292. mLastVelocity = velocity;
  293. mLastWidth = newWidth;
  294. // Remove the first element from the list,
  295. // so that we always have no more than 4 mPoints in mPoints array.
  296. mPoints.remove(0);
  297. }
  298. }
  299. private void addBezier(Bezier curve, float startWidth, float endWidth) {
  300. ensureSignatureBitmap();
  301. float originalWidth = mPaint.getStrokeWidth();
  302. float widthDelta = endWidth - startWidth;
  303. float drawSteps = (float) Math.floor(curve.length());
  304. for (int i = 0; i < drawSteps; i++) {
  305. // Calculate the Bezier (x, y) coordinate for this step.
  306. float t = ((float) i) / drawSteps;
  307. float tt = t * t;
  308. float ttt = tt * t;
  309. float u = 1 - t;
  310. float uu = u * u;
  311. float uuu = uu * u;
  312. float x = uuu * curve.startPoint.x;
  313. x += 3 * uu * t * curve.control1.x;
  314. x += 3 * u * tt * curve.control2.x;
  315. x += ttt * curve.endPoint.x;
  316. float y = uuu * curve.startPoint.y;
  317. y += 3 * uu * t * curve.control1.y;
  318. y += 3 * u * tt * curve.control2.y;
  319. y += ttt * curve.endPoint.y;
  320. // Set the incremental stroke width and draw.
  321. mPaint.setStrokeWidth(startWidth + ttt * widthDelta);
  322. mSignatureBitmapCanvas.drawPoint(x, y, mPaint);
  323. expandDirtyRect(x, y);
  324. }
  325. mPaint.setStrokeWidth(originalWidth);
  326. }
  327. private ControlTimedPoints calculateCurveControlPoints(TimedPoint s1, TimedPoint s2, TimedPoint s3) {
  328. float dx1 = s1.x - s2.x;
  329. float dy1 = s1.y - s2.y;
  330. float dx2 = s2.x - s3.x;
  331. float dy2 = s2.y - s3.y;
  332. TimedPoint m1 = new TimedPoint((s1.x + s2.x) / 2.0f, (s1.y + s2.y) / 2.0f);
  333. TimedPoint m2 = new TimedPoint((s2.x + s3.x) / 2.0f, (s2.y + s3.y) / 2.0f);
  334. float l1 = (float) Math.sqrt(dx1 * dx1 + dy1 * dy1);
  335. float l2 = (float) Math.sqrt(dx2 * dx2 + dy2 * dy2);
  336. float dxm = (m1.x - m2.x);
  337. float dym = (m1.y - m2.y);
  338. float k = l2 / (l1 + l2);
  339. TimedPoint cm = new TimedPoint(m2.x + dxm * k, m2.y + dym * k);
  340. float tx = s2.x - cm.x;
  341. float ty = s2.y - cm.y;
  342. return new ControlTimedPoints(new TimedPoint(m1.x + tx, m1.y + ty), new TimedPoint(m2.x + tx, m2.y + ty));
  343. }
  344. private float strokeWidth(float velocity) {
  345. return Math.max(mMaxWidth / (velocity + 1), mMinWidth);
  346. }
  347. /**
  348. * Called when replaying history to ensure the dirty region includes all
  349. * mPoints.
  350. *
  351. * @param historicalX
  352. * the previous x coordinate.
  353. * @param historicalY
  354. * the previous y coordinate.
  355. */
  356. private void expandDirtyRect(float historicalX, float historicalY) {
  357. if (historicalX < mDirtyRect.left) {
  358. mDirtyRect.left = historicalX;
  359. } else if (historicalX > mDirtyRect.right) {
  360. mDirtyRect.right = historicalX;
  361. }
  362. if (historicalY < mDirtyRect.top) {
  363. mDirtyRect.top = historicalY;
  364. } else if (historicalY > mDirtyRect.bottom) {
  365. mDirtyRect.bottom = historicalY;
  366. }
  367. }
  368. /**
  369. * Resets the dirty region when the motion event occurs.
  370. *
  371. * @param eventX
  372. * the event x coordinate.
  373. * @param eventY
  374. * the event y coordinate.
  375. */
  376. private void resetDirtyRect(float eventX, float eventY) {
  377. // The mLastTouchX and mLastTouchY were set when the ACTION_DOWN motion
  378. // event occurred.
  379. mDirtyRect.left = Math.min(mLastTouchX, eventX);
  380. mDirtyRect.right = Math.max(mLastTouchX, eventX);
  381. mDirtyRect.top = Math.min(mLastTouchY, eventY);
  382. mDirtyRect.bottom = Math.max(mLastTouchY, eventY);
  383. }
  384. private void setIsEmpty(boolean newValue) {
  385. mIsEmpty = newValue;
  386. if (mOnSignedListener != null) {
  387. if (mIsEmpty) {
  388. mOnSignedListener.onClear();
  389. } else {
  390. mOnSignedListener.onSigned();
  391. }
  392. }
  393. }
  394. private void ensureSignatureBitmap() {
  395. if (mSignatureBitmap == null) {
  396. mSignatureBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
  397. mSignatureBitmapCanvas = new Canvas(mSignatureBitmap);
  398. }
  399. }
  400. private int convertDpToPx(float dp) {
  401. return Math.round(dp * (getResources().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
  402. }
  403. public interface OnSignedListener {
  404. public void onSigned();
  405. public void onClear();
  406. }
  407. }


Demo源码下载链接

最后我集成到项目里面的效果图 如上图

如果大家有其他问题 欢迎加入我的qq群讨论  开发一群:454430053开发二群:537532956

文章来源: wukong.blog.csdn.net,作者:再见孙悟空_,版权归原作者所有,如需转载,请联系作者。

原文链接:wukong.blog.csdn.net/article/details/77993757

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。