Kevin Learn Android:Android 手签板
Posted Kevin_小飞象
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kevin Learn Android:Android 手签板相关的知识,希望对你有一定的参考价值。
前言
android 屏幕手写签名的原理就是把手机屏幕当作画板,把用户手指当作画笔,手指在屏幕上在屏幕上划来划去,屏幕就会显示手指的移动轨迹,就像画笔在画板上写字一样。实现手写签名需要结合绘图的路径工具 Path ,在有按下动作时调用 Path 对象的 moveTo 方法,将路径起始点移动到触摸点;在有移动操作时调用 Path 对象的 quadTo 方法,将记录本次触摸点与上次触摸点之间的路径;在有移动操作与提起动作时调用 Canvas 对象的 drawPath 方法,将本次触摸绘制在画布上。
效果图
功能
- 空白画板手写
- 实现笔锋效果
- 支持橡皮擦,撤回/恢复,清空画布功能
- 支持画笔颜色大小设置
- 支持传入初始图片
- 支持画布大小设置,文字区域裁剪
- 主题颜色设置
- 支持传入初始显示图片## 代码
1. 布局文件
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/rl_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:clipToPadding="true"
android:fitsSystemWindows="true"
android:orientation="vertical">
<include
android:id="@+id/actionbar"
layout="@layout/sign_actionbar"
android:layout_width="match_parent"
android:layout_height="@dimen/sign_actionbar_height"
android:layout_alignParentTop="true" />
<com.hkt.handwritten.view.PaintView
android:id="@+id/paint_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/divider"
android:layout_below="@id/actionbar"
android:layout_margin="12dp"
android:background="@drawable/shape_dash_bg"/>
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_above="@+id/setting"
android:background="@color/sign_border_gray" />
<LinearLayout
android:id="@+id/setting"
android:layout_width="match_parent"
android:layout_height="@dimen/y105"
android:layout_alignParentBottom="true"
android:background="@drawable/bottom_bg_shape"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@+id/btn_hand"
android:layout_width="@dimen/x38"
android:layout_height="@dimen/y38"
android:layout_alignParentLeft="true"
android:layout_marginRight="@dimen/x40"
android:scaleType="centerCrop"
android:layout_weight="1"
android:background="@drawable/sign_bg_btn_clicked"
android:src="@mipmap/sign_ic_hand"
android:visibility="gone" />
<ImageView
android:id="@+id/btn_undo"
android:layout_width="@dimen/x78"
android:layout_height="@dimen/y78"
android:scaleType="centerCrop"
android:layout_marginRight="@dimen/x40"
android:layout_toLeftOf="@+id/btn_redo"
android:padding="@dimen/x12"
android:background="@drawable/sign_bg_btn_clicked"
android:src="@mipmap/sign_ic_undo" />
<ImageView
android:id="@+id/btn_redo"
android:layout_width="@dimen/x78"
android:layout_height="@dimen/y78"
android:scaleType="centerCrop"
android:layout_marginRight="@dimen/x40"
android:layout_toLeftOf="@+id/btn_clear"
android:background="@drawable/sign_bg_btn_clicked"
android:padding="@dimen/x12"
android:src="@mipmap/sign_ic_redo" />
<ImageView
android:id="@+id/btn_pen"
android:layout_width="@dimen/x78"
android:layout_height="@dimen/y78"
android:scaleType="centerCrop"
android:layout_marginRight="@dimen/x40"
android:layout_toLeftOf="@+id/btn_setting"
android:padding="@dimen/x12"
android:background="@drawable/sign_bg_btn_clicked"
android:src="@mipmap/sign_ic_pen" />
<ImageView
android:id="@+id/btn_clear"
android:layout_width="@dimen/x78"
android:layout_height="@dimen/y78"
android:layout_marginRight="@dimen/x40"
android:layout_toLeftOf="@+id/btn_pen"
android:padding="@dimen/x12"
android:scaleType="centerCrop"
android:background="@drawable/sign_bg_btn_clicked"
android:src="@mipmap/sign_ic_clear" />
<com.hkt.handwritten.view.CircleView
android:id="@+id/btn_setting"
android:layout_width="@dimen/x78"
android:layout_height="@dimen/y78"
android:layout_alignParentRight="true"
android:padding="@dimen/x12"
app:showOutBorder="false"
app:sizeLevel="2" />
</LinearLayout>
</RelativeLayout>
2. 核心代码 PaintView.java
/**
* Created on 2021/12/1 11:58
*
* @author Gong Youqiang
*/
public class PaintView extends View
public static final int TYPE_PEN = 0;
public static final int TYPE_ERASER = 1;
private Paint mPaint;
private Canvas mCanvas;
private Bitmap mBitmap;
private int strokeWidth;
private BasePen mStokeBrushPen;
/**
* 是否允许写字
*/
private boolean isFingerEnable = true;
/**
* 是否橡皮擦模式
*/
private boolean isEraser = false;
/**
* 是否有绘制
*/
private boolean hasDraw = false;
/**
* 画笔轨迹记录
*/
private StepOperator mStepOperation;
private StepCallback mCallback;
/**
* 是否可以撤销
*/
private boolean mCanUndo;
/**
* 是否可以恢复
*/
private boolean mCanRedo;
private int mWidth;
private int mHeight;
private boolean isDrawing = false;//是否正在绘制
private int toolType = 0; //记录手写笔类型:触控笔/手指
private Eraser eraser;
public PaintView(Context context)
this(context, null);
public PaintView(Context context, AttributeSet attrs)
this(context, attrs, 0);
public PaintView(Context context, AttributeSet attrs, int defStyleAttr)
super(context, attrs, defStyleAttr);
/**
* 初始化画板
*
* @param width 画板宽度
* @param height 画板高度
* @param path 初始图片路径
*/
public void init(int width, int height, String path)
this.mWidth = width;
this.mHeight = height;
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_4444);
mStokeBrushPen = new SteelPen();
initPaint();
initCanvas();
mStepOperation = new StepOperator();
if (!TextUtils.isEmpty(path))
Bitmap bitmap = BitmapFactory.decodeFile(path);
resize(bitmap, mWidth, mHeight);
else
mStepOperation.addBitmap(mBitmap);
//橡皮擦
eraser = new Eraser(getResources().getDimensionPixelSize(R.dimen.sign_eraser_size));
/**
* 初始画笔设置
*/
private void initPaint()
strokeWidth = DisplayUtil.dip2px(getContext(), PaintSettingWindow.PEN_SIZES[PenConfig.PAINT_SIZE_LEVEL]);
mPaint = new Paint();
mPaint.setColor(PenConfig.PAINT_COLOR);
mPaint.setStrokeWidth(strokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setAlpha(0xFF);
mPaint.setAntiAlias(true);
mPaint.setStrokeMiter(1.0f);
mStokeBrushPen.setPaint(mPaint);
private void initCanvas()
mCanvas = new Canvas(mBitmap);
//设置画布的背景色为透明
mCanvas.drawColor(Color.TRANSPARENT);
@Override
protected void onDraw(Canvas canvas)
canvas.drawBitmap(mBitmap, 0, 0, mPaint);
if (!isEraser)
mStokeBrushPen.draw(canvas);
super.onDraw(canvas);
@Override
public boolean dispatchTouchEvent(MotionEvent ev)
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
@Override
public boolean onTouchEvent(MotionEvent event)
toolType = event.getToolType(event.getActionIndex());
if (!isFingerEnable && toolType != MotionEvent.TOOL_TYPE_STYLUS)
return false;
if (isEraser)
eraser.handleEraserEvent(event, mCanvas);
else
mStokeBrushPen.onTouchEvent(event, mCanvas);
switch (event.getActionMasked())
case MotionEvent.ACTION_DOWN:
isDrawing = false;
break;
case MotionEvent.ACTION_MOVE:
hasDraw = true;
mCanUndo = true;
isDrawing = true;
break;
case MotionEvent.ACTION_CANCEL:
isDrawing = false;
break;
case MotionEvent.ACTION_UP:
if (mStepOperation != null && isDrawing)
mStepOperation.addBitmap(mBitmap);
mCanUndo = !mStepOperation.currentIsFirst();
mCanRedo = !mStepOperation.currentIsLast();
if (mCallback != null)
mCallback.onOperateStatusChanged();
isDrawing = false;
break;
default:
break;
invalidate();
return true;
/**
* @return 判断是否有绘制内容在画布上
*/
public boolean isEmpty()
return !hasDraw;
/**
* 撤销
*/
public void undo()
if (mStepOperation == null || !mCanUndo)
return;
if (!mStepOperation.currentIsFirst())
mCanUndo = true;
mStepOperation.undo(mBitmap);
hasDraw = true;
invalidate();
if (mStepOperation.currentIsFirst())
mCanUndo = false;
hasDraw = false;
else
mCanUndo = false;
hasDraw = false;
if (!mStepOperation.currentIsLast())
mCanRedo = true;
if (mCallback != null)
mCallback.onOperateStatusChanged();
/**
* 恢复
*/
public void redo()
if (mStepOperation == null || !mCanRedo)
return;
if (!mStepOperation.currentIsLast())
mCanRedo = true;
mStepOperation.redo(mBitmap);
hasDraw = true;
invalidate();
if (mStepOperation.currentIsLast())
mCanRedo = false;
else
mCanRedo = false;
if (!mStepOperation.currentIsFirst())
mCanUndo = true;
if (mCallback != null)
mCallback.onOperateStatusChanged();
/**
* 清除画布,记得清除点的集合
*/
public void reset()
mBitmap.eraseColor(Color.TRANSPARENT);
hasDraw = false;
mStokeBrushPen.clear();
if (mStepOperation != null)
mStepOperation.reset();
mStepOperation.addBitmap(mBitmap);
mCanRedo = false;
mCanUndo = false;
if (mCallback != null)
mCallback.onOperateStatusChanged();
invalidate();
public void release()
destroyDrawingCache();
if (mBitmap != null)
mBitmap.recycle();
mBitmap = null;
if (mStepOperation != null)
mStepOperation.freeBitmaps();
mStepOperation = null;
public interface StepCallback
/**
* 操作变更
*/
void onOperateStatusChanged();
public void setStepCallback(StepCallback callback)
this.mCallback = callback;
/**
* 设置画笔样式
*
* @param penType
*/
public void setPenType(int penType)
isEraser = false;
switch (penType)
case TYPE_PEN:
mStokeBrushPen = new SteelPen();
break;
case TYPE_ERASER:
isEraser = true;
break;
//设置
if (mStokeBrushPen.isNullPaint())
mStokeBrushPen.setPaint(mPaint);
invalidate();
/**
* 设置画笔大小
*
* @param width 大小
*/
public void setPaintWidth(int width)
if (mPaint != null)
mPaint.setStrokeWidth(DisplayUtil.dip2px(getContext(), width));
// eraser.setPaintWidth(DisplayUtil.dip2px(getContext(), width));
mStokeBrushPen.setPaint(mPaint);
invalidate();
/**
* 设置画笔颜色
*
* @param color 颜色
*/
public void setPaintColor(int color)
if (mPaint != null)
mPaint.setColor(color);
mStokeBrushPen.setPaint(mPaint);
invalidate();
/**
* 构建Bitmap
*
* @return 所绘制的bitmap
*/
public Bitmap buildAreaBitmap(boolean isCrop)
if (!hasDraw)
return null;
Bitmap result;
if (isCrop)
result = BitmapUtil.clearBlank(mBitmap, 50, Color.TRANSPARENT);
else
result = mBitmap;
destroyDrawingCache();
return result;
public boolean isFingerEnable()
return isFingerEnable;
public void setFingerEnable(boolean fingerEnable)
isFingerEnable = fingerEnable;
public boolean isEraser()
return isEraser;
public boolean canUndo()
return mCanUndo;
public boolean canRedo()
return mCanRedo;
public Bitmap getBitmap()
return mBitmap;
/**
* 图片大小调整适配画布宽高
*
* @param bitmap 源图
* @param width 新宽度
* @param height 新高度
*/
public void resize(Bitmap bitmap, int width, int height)
if (mBitmap != null)
if (width >= this.mWidth)
height = width * mBitmap.getHeight() / mBitmap.getWidth();
this.mWidth = width;
this.mHeight = height;
mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444);
restoreLastBitmap(bitmap, mBitmap);
initCanvas();
if (mStepOperation != null)
mStepOperation.addBitmap(mBitmap);
invalidate();
/**
* 恢复最后画的bitmap
*
* @param srcBitmap 最后的bitmap
* @param newBitmap 新bitmap
*/
private void restoreLastBitmap(Bitmap srcBitmap, Bitmap newBitmap)
try
if (srcBitmap == null || srcBitmap.isRecycled())
return;
srcBitmap = BitmapUtil.zoomImg(srcBitmap, newBitmap.getWidth());
//缩放后如果还是超出新图宽高,继续缩放
if (srcBitmap.getWidth() > newBitmap.getWidth() || srcBitmap.getHeight() > newBitmap.getHeight())
srcBitmap = BitmapUtil.zoomImage(srcBitmap, newBitmap.getWidth(), newBitmap.getHeight());
//保存所有的像素的数组,图片宽×高
int[] pixels = new int[srcBitmap.getWidth() * srcBitmap.getHeight()];
srcBitmap.getPixels(pixels, 0, srcBitmap.getWidth(), 0, 0, srcBitmap.getWidth(), srcBitmap.getHeight());
newBitmap.setPixels(pixels, 0, srcBitmap.getWidth(), 0, 0,
srcBitmap.getWidth(), srcBitmap.getHeight());
catch (OutOfMemoryError e)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
int width = onMeasureR(0, widthMeasureSpec);
int height = onMeasureR(1, heightMeasureSpec);
setMeasuredDimension(width, height);
/**
* 计算控件宽高
*/
public int onMeasureR(int attr, int oldMeasure)
int newSize = 0;
int mode = MeasureSpec.getMode(oldMeasure);
int oldSize = MeasureSpec.getSize(oldMeasure);
switch (mode)
case MeasureSpec.EXACTLY:
newSize = oldSize;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.UNSPECIFIED:
float value = mWidth;
if (attr == 0)
if (mBitmap != null)
value = mBitmap.getWidth();
// 控件的宽度
newSize = (int) (getPaddingLeft() + value + getPaddingRight());
else if (attr == 1)
value = mHeight;
if (mBitmap != null)
value = mBitmap.getHeight();
// 控件的高度
newSize = (int) (getPaddingTop() + value + getPaddingBottom());
break;
default:
break;
return newSize;
public Bitmap getLastBitmap()
return mBitmap;
3. 核心代码 SteelPen.java
/**
* Created on 2021/12/1 11:38
* 钢笔
* @author Gong Youqiang
*/
public class SteelPen extends BasePen
@Override
protected void doPreDraw(Canvas canvas)
for (int i = 1; i < mHWPointList.size(); i++)
ControllerPoint point = mHWPointList.get(i);
drawToPoint(canvas, point, mPaint);
mCurPoint = point;
@Override
protected void doMove(double curDis)
int steps = 1 + (int) curDis / STEP_FACTOR;
double step = 1.0 / steps;
for (double t = 0; t < 1.0; t += step)
ControllerPoint point = mBezier.getPoint(t);
mHWPointList.add(point);
@Override
protected void doDraw(Canvas canvas, ControllerPoint point, Paint paint)
drawLine(canvas, mCurPoint.x, mCurPoint.y, mCurPoint.width, point.x,
point.y, point.width, paint);
/**
* 绘制方法,实现笔锋效果
*/
private void drawLine(Canvas canvas, double x0, double y0, double w0, double x1, double y1, double w1, Paint paint)
//求两个数字的平方根 x的平方+y的平方在开方记得X的平方+y的平方=1,这就是一个园
double curDis = Math.hypot(x0 - x1, y0 - y1);
int steps;
//绘制的笔的宽度是多少,绘制多少个椭圆
if (paint.getStrokeWidth() < 6)
steps = 1 + (int) (curDis / 2);
else if (paint.getStrokeWidth() > 60)
steps = 1 + (int) (curDis / 4);
else
steps = 1 + (int) (curDis / 3);
double deltaX = (x1 - x0) / steps;
double deltaY = (y1 - y0) / steps;
double deltaW = (w1 - w0) / steps;
double x = x0;
double y = y0;
double w = w0;
for (int i = 0; i < steps; i++)
RectF oval = new RectF();
float top = (float) (y - w / 2.0f);
float left = (float) (x - w / 4.0f);
float right = (float) (x + w / 4.0f);
float bottom = (float) (y + w / 2.0f);
oval.set(left, top, right, bottom);
//最基本的实现,通过点控制线,绘制椭圆
canvas.drawOval(oval, paint);
x += deltaX;
y += deltaY;
w += deltaW;
4. 使用
public class HandWrittenBoardActivity extends BaseActivity implements View.OnClickListener, PaintView.StepCallback
private static final int REQUEST_EXTERNAL_STORAGE = 1;
private ImageView mHandView; //切换 滚动/手写
private ImageView mUndoView;
private ImageView mRedoView;
private ImageView mPenView;
private ImageView mClearView;
private CircleView mSettingView;
private PaintView mPaintView;
private ProgressDialog mSaveProgressDlg;
private static final int MSG_SAVE_SUCCESS = 1;
private static final int MSG_SAVE_FAILED = 2;
private String mSavePath;
private boolean hasSize = false;
private float mWidth;
private float mHeight;
private float widthRate = 1.0f;
private float heightRate = 1.0f;
private int bgColor;
private boolean isCrop;
private String format;
private PaintSettingWindow settingWindow;
private static String[] PERMISSIONS_STORAGE =
"android.permission.READ_EXTERNAL_STORAGE",
"android.permission.WRITE_EXTERNAL_STORAGE" ;
@Override
protected int getLayout()
return R.layout.activity_hand_written_board;
@Override
protected void initView()
verifyStoragePermissions(this);
View mCancelView = findViewById(R.id.tv_cancel);
View mOkView = findViewById(R.id.tv_ok);
mPaintView = findViewById(R.id.paint_view);
mHandView = findViewById(R.id.btn_hand);
mUndoView = findViewById(R.id.btn_undo);
mRedoView = findViewById(R.id.btn_redo);
mPenView = findViewById(R.id.btn_pen);
mClearView = findViewById(R.id.btn_clear);
mSettingView = findViewById(R.id.btn_setting);
mUndoView.setOnClickListener(this);
mRedoView.setOnClickListener(this);
mPenView.setOnClickListener(this);
mClearView.setOnClickListener(this);
mSettingView.setOnClickListener(this);
mHandView.setOnClickListener(this);
mCancelView.setOnClickListener(this);
mOkView.setOnClickListener(this);
mPenView.setSelected(true);
mUndoView.setEnabled(false);
mRedoView.setEnabled(false);
mClearView.setEnabled(!mPaintView.isEmpty());
mPaintView.setStepCallback(this);
PenConfig.PAINT_SIZE_LEVEL = PenConfig.getPaintTextLevel(this);
PenConfig.PAINT_COLOR = PenConfig.getPaintColor(this);
mSettingView.setPaintColor(PenConfig.PAINT_COLOR);
mSettingView.setRadiusLevel(PenConfig.PAINT_SIZE_LEVEL);
setThemeColor(PenConfig.THEME_COLOR);
BitmapUtil.setImage(mClearView, R.mipmap.sign_ic_clear, Color.WHITE);
BitmapUtil.setImage(mPenView, R.mipmap.sign_ic_pen, Color.WHITE);
BitmapUtil.setImage(mRedoView, R.mipmap.sign_ic_redo, mPaintView.canRedo() ? Color.WHITE : Color.LTGRAY);
BitmapUtil.setImage(mUndoView, R.mipmap.sign_ic_undo, mPaintView.canUndo() ? Color.WHITE : Color.LTGRAY);
BitmapUtil.setImage(mClearView, R.mipmap.sign_ic_clear, !mPaintView.isEmpty() ? Color.WHITE : Color.LTGRAY);
/**
* 获取画布默认宽度
*
* @return
*/
private int getResizeWidth()
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE && dm.widthPixels < dm.heightPixels)
return (int) (dm.heightPixels * widthRate);
return (int) (dm.widthPixels * widthRate);
/**
* 获取画布默认高度
*
* @return
*/
private int getResizeHeight()
int toolBarHeight = getResources().getDimensionPixelSize(R.dimen.sign_grid_toolbar_height);
int actionbarHeight = getResources().getDimensionPixelSize(R.dimen.sign_actionbar_height);
int statusBarHeight = StatusBarCompat.getStatusBarHeight(this);
int otherHeight = toolBarHeight + actionbarHeight + statusBarHeight;
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE && dm.widthPixels < dm.heightPixels)
return (int) ((dm.widthPixels - otherHeight) * heightRate);
return (int) ((dm.heightPixels - otherHeight) * heightRate);
@Override
protected void initData()
isCrop = getIntent().getBooleanExtra("crop", false);
format = getIntent().getStringExtra("format");
bgColor = getIntent().getIntExtra("background", Color.TRANSPARENT);
String mInitPath = getIntent().getStringExtra("image");
float bitmapWidth = getIntent().getFloatExtra("width", 1.0f);
float bitmapHeight = getIntent().getFloatExtra("height", 1.0f);
if (bitmapWidth > 0 && bitmapWidth <= 1.0f)
widthRate = bitmapWidth;
mWidth = getResizeWidth();
else
hasSize = true;
mWidth = bitmapWidth;
if (bitmapHeight > 0 && bitmapHeight <= 1.0f)
heightRate = bitmapHeight;
mHeight = getResizeHeight();
else
hasSize = true;
mHeight = bitmapHeight;
//初始画板设置
if (!hasSize && !TextUtils.isEmpty(mInitPath))
Bitmap bitmap = BitmapFactory.decodeFile(mInitPath);
mWidth = bitmap.getWidth();
mHeight = bitmap.getHeight();
hasSize = true;
mPaintView.init((int) mWidth, (int) mHeight, mInitPath);
if (bgColor != Color.TRANSPARENT)
mPaintView.setBackgroundColor(bgColor);
/**
* 横竖屏切换
*/
@Override
public void onConfigurationChanged(Configuration newConfig)
super.onConfigurationChanged(newConfig);
if (settingWindow != null)
settingWindow.dismiss();
int resizeWidth = getResizeWidth();
int resizeHeight = getResizeHeight();
if (mPaintView != null && !hasSize)
mPaintView.resize(mPaintView.getLastBitmap(), resizeWidth, resizeHeight);
public static void verifyStoragePermissions(Activity activity)
try
//检测是否有写的权限
int permission = ActivityCompat.checkSelfPermission(activity,
"android.permission.WRITE_EXTERNAL_STORAGE");
if (permission != PackageManager.PERMISSION_GRANTED)
// 没有写的权限,去申请写的权限,会弹出对话框
ActivityCompat.requestPermissions(activity, PERMISSIONS_STORAGE,REQUEST_EXTERNAL_STORAGE);
catch (Exception e)
e.printStackTrace();
@Override
public void onClick(View v)
int i = v.getId();
if (i == R.id.btn_setting)
showPaintSettingWindow();
else if (i == R.id.btn_hand)
//切换是否允许写字
mPaintView.setFingerEnable(!mPaintView.isFingerEnable());
if (mPaintView.isFingerEnable())
BitmapUtil.setImage(mHandView, R.mipmap.sign_ic_hand, PenConfig.THEME_COLOR);
else
BitmapUtil.setImage(mHandView, R.mipmap.sign_ic_drag, PenConfig.THEME_COLOR);
else if (i == R.id.btn_clear)
mPaintView.reset();
else if (i == R.id.btn_undo)
mPaintView.undo();
else if (i == R.id.btn_redo)
mPaintView.redo();
else if (i == R.id.btn_pen)
if (!mPaintView.isEraser())
mPaintView.setPenType(PaintView.TYPE_ERASER);
BitmapUtil.setImage(mPenView, R.mipmap.sign_ic_eraser, Color.WHITE);
else
mPaintView.setPenType(PaintView.TYPE_PEN);
BitmapUtil.setImage(mPenView, R.mipmap.sign_ic_pen, Color.WHITE);
else if (i == R.id.tv_ok)
save();
else if (i == R.id.tv_cancel)
if (!mPaintView.isEmpty())
showQuitTip();
else
setResult(RESULT_CANCELED);
finish();
@Override
protected void onDestroy()
if (mPaintView != null)
mPaintView.release();
if (mHandler != null)
mHandler.removeCallbacksAndMessages(null);
super.onDestroy();
/**
* 弹出画笔设置
*/
private void showPaintSettingWindow()
settingWindow = new PaintSettingWindow(this);
settingWindow.setSettingListener(new PaintSettingWindow.OnSettingListener()
@Override
public void onColorSetting(int color)
mPaintView.setPaintColor(color);
mSettingView.setPaintColor(color);
@Override
public void onSizeSetting(int index)
mSettingView.setRadiusLevel(index);
mPaintView.setPaintWidth(PaintSettingWindow.PEN_SIZES[index]);
);
View contentView = settingWindow.getContentView();
//需要先测量,PopupWindow还未弹出时,宽高为0
contentView.measure(SystemUtil.makeDropDownMeasureSpec(settingWindow.getWidth()),
SystemUtil.makeDropDownMeasureSpec(settingWindow.getHeight()));
int padding = DisplayUtil.dip2px(this, 45);
settingWindow.popAtTopRight();
settingWindow.showAsDropDown(mSettingView, -540, -2 * padding - settingWindow.getContentView().getMeasuredHeight());
private void initSaveProgressDlg()
mSaveProgressDlg = new ProgressDialog(this);
mSaveProgressDlg.setMessage("正在保存,请稍候...");
mSaveProgressDlg.setCancelable(false);
@SuppressLint("HandlerLeak")
private Handler mHandler = new Handler()
@Override
public void handleMessage(Message msg)
switch (msg.what)
case MSG_SAVE_FAILED:
mSaveProgressDlg.dismiss();
Toast.makeText(getApplicationContext(), "保存失败", Toast.LENGTH_SHORT).show();
break;
case MSG_SAVE_SUCCESS:
mSaveProgressDlg.dismiss();
Intent intent = new Intent();
intent.putExtra(PenConfig.SAVE_PATH, mSavePath);
setResult(RESULT_OK, intent);
break;
default:
break;
;
/**
* 保存
*/
private void save()
if (mPaintView.isEmpty())
Toast.makeText(getApplicationContext(), "没有写入任何文字", Toast.LENGTH_SHORT).show();
return;
//先检查是否有存储权限
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
Toast.makeText(getApplicationContext(), "没有读写存储的权限", Toast.LENGTH_SHORT).show();
return;
if (mSaveProgressDlg == null)
initSaveProgressDlg();
mSaveProgressDlg.show();
new Thread(() ->
try
Bitmap result = mPaintView.buildAreaBitmap(isCrop);
if (PenConfig.FORMAT_JPG.equals(format) && bgColor == Color.TRANSPARENT)
bgColor = Color.WHITE;
if (bgColor != Color.TRANSPARENT)
result = BitmapUtil.drawBgToBitmap(result, bgColor);
if (result == null)
mHandler.obtainMessage(MSG_SAVE_FAILED).sendToTarget();
return;
mSavePath = BitmapUtil.saveImage(HandWrittenBoardActivity.this, result, 100, format);
if (mSavePath != null)
mHandler.obtainMessage(MSG_SAVE_SUCCESS).sendToTarget();
else
mHandler.obtainMessage(MSG_SAVE_FAILED).sendToTarget();
catch (Exception e)
).start();
/**
* 画布有操作
*/
@Override
public void onOperateStatusChanged()
mUndoView.setEnabled(mPaintView.canUndo());
mRedoView.setEnabled(mPaintView.canRedo());
mClearView.setEnabled(!mPaintView.isEmpty());
BitmapUtil.setImage(mRedoView, R.mipmap.sign_ic_redo, mPaintView.canRedo() ? Color.WHITE : Color.LTGRAY);
BitmapUtil.setImage(mUndoView, R.mipmap.sign_ic_undo, mPaintView.canUndo() ? Color.WHITE : Color.LTGRAY);
BitmapUtil.setImage(mClearView, R.mipmap.sign_ic_clear, !mPaintView.isEmpty() ? Color.WHITE : Color.LTGRAY);
@Override
public void onBackPressed()
if (!mPaintView.isEmpty())
showQuitTip();
else
setResult(RESULT_CANCELED);
finish();
/**
* 弹出退出提示
*/
private void showQuitTip()
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("提示")
.setMessage("当前文字未保存,是否退出?")
.setNegativeButton("取消", null)
.setPositiveButton("确定", (dialog, which) ->
setResult(RESULT_CANCELED);
finish();
);
builder.show();
[Demo](https://download.csdn.net/download/duoduo_11011/63793446)
以上是关于Kevin Learn Android:Android 手签板的主要内容,如果未能解决你的问题,请参考以下文章
Kevin Learn Android:Android 手签板
Kevin Learn Android:Android 手签板
Kevin Learn Android--> Android Studio 小技巧
Kevin Learn Android-->NFC 技术解析