Android 自定義View--手寫簽名
阿新 • • 發佈:2019-02-12
1、概述
1.1 目的 :
在我們的日常開發中,有很多Android UI介面上有一些特殊或者特別的控制元件與介面,是Android自帶的控制元件所不能滿足的,需要我們自己定製一些適合的控制元件來完成。
1.2 Android自定義View步驟 :
- 自定義屬性;
- 選擇和設定構造方法;
- 重寫onMeasure()方法;
- 重寫onDraw()方法;
- 重寫onLayout()方法;
- 重寫其他事件的方法(滑動監聽等)。
2、程式碼實現
2.1 自定義屬性:
我們通常將自定義屬性定義在/values/attr.xml檔案中(attr.xml檔案需要自己建立)。
2.2 實現方法含義
1、在OnMeasure()方法中,測量自定義控制元件的大小,使自定義控制元件能夠自適應佈局各種各樣的需求。
2、在OnDraw()方法中,利用哼哈二將(Canvas與Paint)來繪製要顯示的內容。
3、在OnLayout()方法中來確定控制元件顯示位置。
4、在OnTouchEvent()方法處理控制元件的觸控事件。、
2.3 繼承View實現程式碼
package com.fly.myview; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.support.annotation.ColorInt; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; /** * <pre> * .----. * _.'__ `. * .--(Q)(OK)---/$\ * .' @ /$$$\ * : , $$$$$ * `-..__.-' _.-\$$$/ * `;_: `"' * .'"""""`. * /, FLY ,\ * // \\ * `-._______.-' * ___`. | .'___ * (______|______) * </pre> * 包 名 : com.fly.myview * 作 者 : FLY * 建立時間 : 2018/3/2 * 描述: 手寫簽名 */ public class LinePathView extends View { private Context mContext; /** * 筆畫X座標起點 */ private float mX; /** * 筆畫Y座標起點 */ private float mY; /** * 手寫畫筆 */ private final Paint mGesturePaint = new Paint(); /** * 路徑 */ private final Path mPath = new Path(); /** * 簽名畫筆 */ private Canvas cacheCanvas; /** * 簽名畫布 */ private Bitmap cachebBitmap; /** * 是否已經簽名 */ private boolean isTouched = false; /** * 畫筆寬度 px; */ private int mPaintWidth = 10; /** * 前景色 */ private int mPenColor = Color.BLACK; /** * 背景色(指最終簽名結果檔案的背景顏色,預設為透明色) */ private int mBackColor = Color.TRANSPARENT; //簽名開始與結束 private Touch touch; public LinePathView(Context context) { super(context); init(context); } public LinePathView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public LinePathView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } public void init(Context context) { this.mContext = context; //設定抗鋸齒 mGesturePaint.setAntiAlias(true); //設定簽名筆畫樣式 mGesturePaint.setStyle(Paint.Style.STROKE); //設定筆畫寬度 mGesturePaint.setStrokeWidth(mPaintWidth); //設定簽名顏色 mGesturePaint.setColor(mPenColor); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); //建立跟view一樣大的bitmap,用來儲存簽名 cachebBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); cacheCanvas = new Canvas(cachebBitmap); cacheCanvas.drawColor(mBackColor); isTouched = false; } @Override public boolean onTouchEvent(MotionEvent event) { if (touch != null) touch.OnTouch(true); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: touchDown(event); break; case MotionEvent.ACTION_MOVE: isTouched = true; if (touch != null) touch.OnTouch(false); touchMove(event); break; case MotionEvent.ACTION_UP: //將路徑畫到bitmap中,即一次筆畫完成才去更新bitmap,而手勢軌跡是實時顯示在畫板上的。 cacheCanvas.drawPath(mPath, mGesturePaint); mPath.reset(); break; } // 更新繪製 invalidate(); return true; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //畫此次筆畫之前的簽名 canvas.drawBitmap(cachebBitmap, 0, 0, mGesturePaint); // 通過畫布繪製多點形成的圖形 canvas.drawPath(mPath, mGesturePaint); } // 手指點下螢幕時呼叫 private void touchDown(MotionEvent event) { // 重置繪製路線 mPath.reset(); float x = event.getX(); float y = event.getY(); mX = x; mY = y; // mPath繪製的繪製起點 mPath.moveTo(x, y); } // 手指在螢幕上滑動時呼叫 private void touchMove(MotionEvent event) { final float x = event.getX(); final float y = event.getY(); final float previousX = mX; final float previousY = mY; final float dx = Math.abs(x - previousX); final float dy = Math.abs(y - previousY); // 兩點之間的距離大於等於3時,生成貝塞爾繪製曲線 if (dx >= 3 || dy >= 3) { // 設定貝塞爾曲線的操作點為起點和終點的一半 float cX = (x + previousX) / 2; float cY = (y + previousY) / 2; // 二次貝塞爾,實現平滑曲線;previousX, previousY為操作點,cX, cY為終點 mPath.quadTo(previousX, previousY, cX, cY); // 第二次執行時,第一次結束呼叫的座標值將作為第二次呼叫的初始座標值 mX = x; mY = y; } } /** * 清除畫板 */ public void clear() { if (cacheCanvas != null) { isTouched = false; //更新畫板資訊 mGesturePaint.setColor(mPenColor); cacheCanvas.drawColor(mBackColor, PorterDuff.Mode.CLEAR); mGesturePaint.setColor(mPenColor); invalidate(); } } public interface Touch { void OnTouch(boolean isTouch); } public Touch getTouch() { return touch; } public void setTouch(Touch touch) { this.touch = touch; } /** * 儲存畫板 * * @param path 儲存到路徑 */ public void save(String path) throws IOException { save(path, false, 0); } /** * 儲存畫板 * * @param path 儲存到路徑 * @param clearBlank 是否清除邊緣空白區域 * @param blank 要保留的邊緣空白距離 */ public void save(String path, boolean clearBlank, int blank) throws IOException { Bitmap bitmap = cachebBitmap; //BitmapUtil.createScaledBitmapByHeight(srcBitmap, 300);// 壓縮圖片 if (clearBlank) { bitmap = clearBlank(bitmap, blank); } ByteArrayOutputStream bos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos); byte[] buffer = bos.toByteArray(); if (buffer != null) { File file = new File(path); if (file.exists()) { file.delete(); } OutputStream outputStream = new FileOutputStream(file); outputStream.write(buffer); outputStream.close(); } } /** * 獲取畫板的bitmap * * @return */ public Bitmap getBitMap() { setDrawingCacheEnabled(true); buildDrawingCache(); Bitmap bitmap = getDrawingCache(); setDrawingCacheEnabled(false); return bitmap; } /** * 逐行掃描 清楚邊界空白。 * * @param bp * @param blank 邊距留多少個畫素 * @return */ private Bitmap clearBlank(Bitmap bp, int blank) { int HEIGHT = bp.getHeight(); int WIDTH = bp.getWidth(); int top = 0, left = 0, right = 0, bottom = 0; int[] pixs = new int[WIDTH]; boolean isStop; //掃描上邊距不等於背景顏色的第一個點 for (int y = 0; y < HEIGHT; y++) { bp.getPixels(pixs, 0, WIDTH, 0, y, WIDTH, 1); isStop = false; for (int pix : pixs) { if (pix != mBackColor) { top = y; isStop = true; break; } } if (isStop) { break; } } //掃描下邊距不等於背景顏色的第一個點 for (int y = HEIGHT - 1; y >= 0; y--) { bp.getPixels(pixs, 0, WIDTH, 0, y, WIDTH, 1); isStop = false; for (int pix : pixs) { if (pix != mBackColor) { bottom = y; isStop = true; break; } } if (isStop) { break; } } pixs = new int[HEIGHT]; //掃描左邊距不等於背景顏色的第一個點 for (int x = 0; x < WIDTH; x++) { bp.getPixels(pixs, 0, 1, x, 0, 1, HEIGHT); isStop = false; for (int pix : pixs) { if (pix != mBackColor) { left = x; isStop = true; break; } } if (isStop) { break; } } //掃描右邊距不等於背景顏色的第一個點 for (int x = WIDTH - 1; x > 0; x--) { bp.getPixels(pixs, 0, 1, x, 0, 1, HEIGHT); isStop = false; for (int pix : pixs) { if (pix != mBackColor) { right = x; isStop = true; break; } } if (isStop) { break; } } if (blank < 0) { blank = 0; } //計算加上保留空白距離之後的影象大小 left = left - blank > 0 ? left - blank : 0; top = top - blank > 0 ? top - blank : 0; right = right + blank > WIDTH - 1 ? WIDTH - 1 : right + blank; bottom = bottom + blank > HEIGHT - 1 ? HEIGHT - 1 : bottom + blank; return Bitmap.createBitmap(bp, left, top, right - left, bottom - top); } /** * 設定畫筆寬度 預設寬度為10px * * @param mPaintWidth */ public void setPaintWidth(int mPaintWidth) { mPaintWidth = mPaintWidth > 0 ? mPaintWidth : 10; this.mPaintWidth = mPaintWidth; mGesturePaint.setStrokeWidth(mPaintWidth); } public void setBackColor(@ColorInt int backColor) { mBackColor = backColor; } /** * 設定畫筆顏色 * * @param mPenColor */ public void setPenColor(int mPenColor) { this.mPenColor = mPenColor; mGesturePaint.setColor(mPenColor); } /** * 是否有簽名 * * @return */ public boolean getTouched() { return isTouched; } }
3、使用
<com.fly.myview.LinePathView
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="#FFFFFF" />
//修改背景、筆寬、顏色 mPathView.setBackColor(Color.WHITE); mPathView.setPaintWidth(20); mPathView.setPenColor(Color.BLACK); //清除 mBtnClear.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { mPathView.clear(); mPathView.setBackColor(Color.WHITE); mPathView.setPaintWidth(20); mPathView.setPenColor(Color.BLACK); } }); //儲存 mBtnSave.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (mPathView.getTouched()) { try { mPathView.save("/sdcard/qm.png", true, 10); setResult(100); } catch (IOException e) { e.printStackTrace(); } } else { Toast.makeText(LinePathViewActivity.this, "您沒有簽名~", Toast.LENGTH_SHORT).show(); } } });
4.效果
希望對各位朋友有幫助,謝謝!!!!