本文章是把网上的PaletteView(文章地址 https://www.jianshu.com/p/548d2799fd6e)手写板进行了优化,使得手写更加流畅。
参考资料:https://blog.csdn.net/ekeuy/article/details/37961199
优化方式
- 增加触摸点个数
- 减少每次刷新的区域(每次只刷新一个小矩形区域)
不直接说了,直接上代码。
public class PaletteView extends View {
private Paint mPaint;
private Path mPath;
private float mLastX;
private float mLastY;
private Bitmap mBufferBitmap;
private Canvas mBufferCanvas;
private static final int MAX_CACHE_STEP = 20;
private List<DrawingInfo> mDrawingList;
private List<DrawingInfo> mRemovedList;
private Xfermode mXferModeClear;
private Xfermode mXferModeDraw;
private int mDrawSize;
private int mEraserSize;
private int mPenAlpha = 255;
private boolean mCanEraser;
private Callback mCallback;
public enum Mode {
DRAW,
ERASER
}
private Mode mMode = Mode.DRAW;
public PaletteView(Context context) {
super(context);
init();
}
public PaletteView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public PaletteView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public interface Callback {
void onUndoRedoStatusChanged();
}
public void setCallback(Callback callback) {
mCallback = callback;
}
private void init() {
setDrawingCacheEnabled(true);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setFilterBitmap(true);
mPaint.setStrokeJoin(Paint.Join.ROUND);
mPaint.setStrokeCap(Paint.Cap.ROUND);
mDrawSize = DimenUtils.dp2pxInt(3);
mEraserSize = DimenUtils.dp2pxInt(30);
mPaint.setStrokeWidth(mDrawSize);
mPaint.setColor(Color.BLACK);
mXferModeDraw = new PorterDuffXfermode(PorterDuff.Mode.SRC);
mXferModeClear = new PorterDuffXfermode(PorterDuff.Mode.CLEAR);
mPaint.setXfermode(mXferModeDraw);
}
private void initBuffer() {
mBufferBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
mBufferCanvas = new Canvas(mBufferBitmap);
}
private abstract static class DrawingInfo {
Paint paint;
abstract void draw(Canvas canvas);
}
private static class PathDrawingInfo extends DrawingInfo {
Path path;
@Override
void draw(Canvas canvas) {
canvas.drawPath(path, paint);
}
}
public Mode getMode() {
return mMode;
}
public void setMode(Mode mode) {
if (mode != mMode) {
mMode = mode;
if (mMode == Mode.DRAW) {
mPaint.setXfermode(mXferModeDraw);
mPaint.setStrokeWidth(mDrawSize);
} else {
mPaint.setXfermode(mXferModeClear);
mPaint.setStrokeWidth(mEraserSize);
}
}
}
public void setEraserSize(int size) {
mEraserSize = size;
}
public void setPenRawSize(int size) {
mDrawSize = size;
if (mMode == Mode.DRAW) {
mPaint.setStrokeWidth(mDrawSize);
}
}
public void setPenColor(int color) {
mPaint.setColor(color);
}
private void reDraw() {
if (mDrawingList != null) {
mBufferBitmap.eraseColor(Color.TRANSPARENT);
for (DrawingInfo drawingInfo : mDrawingList) {
drawingInfo.draw(mBufferCanvas);
}
invalidate();
}
}
public int getPenColor() {
return mPaint.getColor();
}
public int getPenSize() {
return mDrawSize;
}
public int getEraserSize() {
return mEraserSize;
}
public void setPenAlpha(int alpha) {
mPenAlpha = alpha;
if (mMode == Mode.DRAW) {
mPaint.setAlpha(alpha);
}
}
public int getPenAlpha() {
return mPenAlpha;
}
public boolean canRedo() {
return mRemovedList != null && mRemovedList.size() > 0;
}
public boolean canUndo() {
return mDrawingList != null && mDrawingList.size() > 0;
}
public void redo() {
int size = mRemovedList == null ? 0 : mRemovedList.size();
if (size > 0) {
DrawingInfo info = mRemovedList.remove(size - 1);
mDrawingList.add(info);
mCanEraser = true;
reDraw();
if (mCallback != null) {
mCallback.onUndoRedoStatusChanged();
}
}
}
public void undo() {
int size = mDrawingList == null ? 0 : mDrawingList.size();
if (size > 0) {
DrawingInfo info = mDrawingList.remove(size - 1);
if (mRemovedList == null) {
mRemovedList = new ArrayList<>(MAX_CACHE_STEP);
}
if (size == 1) {
mCanEraser = false;
}
mRemovedList.add(info);
reDraw();
if (mCallback != null) {
mCallback.onUndoRedoStatusChanged();
}
}
}
public void clear() {
if (mBufferBitmap != null) {
if (mDrawingList != null) {
mDrawingList.clear();
}
if (mRemovedList != null) {
mRemovedList.clear();
}
mCanEraser = false;
mBufferBitmap.eraseColor(Color.TRANSPARENT);
invalidate();
if (mCallback != null) {
mCallback.onUndoRedoStatusChanged();
}
}
}
public Bitmap buildBitmap() {
Bitmap bm = getDrawingCache();
Bitmap result = Bitmap.createBitmap(bm);
destroyDrawingCache();
return result;
}
private void saveDrawingPath() {
if (mDrawingList == null) {
mDrawingList = new ArrayList<>(MAX_CACHE_STEP);
} else if (mDrawingList.size() == MAX_CACHE_STEP) {
mDrawingList.remove(0);
}
Path cachePath = new Path(mPath);
Paint cachePaint = new Paint(mPaint);
PathDrawingInfo info = new PathDrawingInfo();
info.path = cachePath;
info.paint = cachePaint;
mDrawingList.add(info);
mCanEraser = true;
if (mCallback != null) {
mCallback.onUndoRedoStatusChanged();
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mBufferBitmap != null) {
canvas.drawBitmap(mBufferBitmap, 0, 0, null);
}
}
@SuppressWarnings("all")
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!isEnabled()) {
return false;
}
final int action = event.getAction() & MotionEvent.ACTION_MASK;
final float x = event.getX();
final float y = event.getY();
switch (action) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
if (mPath == null) {
mPath = new Path();
}
mPath.moveTo(x, y);
break;
case MotionEvent.ACTION_MOVE:
if (mMode == Mode.DRAW) {
//橡皮擦模式,绘图模式
resetDirtyRect(x, y);
int historySize = event.getHistorySize();
for (int i = 0; i < historySize; i++) {
float historicalX = event.getHistoricalX(i);
float historicalY = event.getHistoricalY(i);
getDirtyRect(historicalX, historicalY);
mPath.lineTo(historicalX, historicalY);
}
mPath.lineTo(x, y);
if (mBufferBitmap == null) {
initBuffer();
}
mBufferCanvas.drawPath(mPath, mPaint);
invalidate((int) (mDirtyRect.left - HALF_STROKE_WIDTH),
(int) (mDirtyRect.top - HALF_STROKE_WIDTH),
(int) (mDirtyRect.right + HALF_STROKE_WIDTH),
(int) (mDirtyRect.bottom + HALF_STROKE_WIDTH));
} else {
//橡皮擦模式
//这里终点设为两点的中心点的目的在于使绘制的曲线更平滑,如果终点直接设置为x,y,效果和lineto是一样的,实际是折线效果
mPath.quadTo(mLastX, mLastY, (x + mLastX) / 2, (y + mLastY) / 2);
if (mBufferBitmap == null) {
initBuffer();
}
if (mMode == Mode.ERASER && !mCanEraser) {
break;
}
mBufferCanvas.drawPath(mPath, mPaint);
invalidate();
}
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_UP:
if (mMode == Mode.DRAW || mCanEraser) {
saveDrawingPath();
}
mPath.reset();
break;
}
return true;
}
private void resetDirtyRect(float eventX, float eventY) {
mDirtyRect.left = Math.min(mLastX, eventX);
mDirtyRect.right = Math.max(mLastX, eventX);
mDirtyRect.top = Math.min(mLastY, eventY);
mDirtyRect.bottom = Math.max(mLastY, eventY);
}
private void getDirtyRect(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;
}
}
private static final float STROKE_WIDTH = 5f;
private static final float HALF_STROKE_WIDTH = STROKE_WIDTH / 2;
private final RectF mDirtyRect = new RectF();
}
使用方式
public class HandWritingFragment extends BaseFragment implements View.OnClickListener, PaletteView.Callback {
@BindView(R.id.mPaletteView)
PaletteView paletteView;
private ImageView mPenView, mClearView, mEraserView, mSaveView, mQuitView;
private ImageView blackPen, redPen, yellowPen, greenPen, pinkPen, purplePen;
private ProgressDialog progressDialog;
private UpLoadImagePresenter upLoadImagePresenter;
private HandWritePrestener handWritePrestener;
@Override
protected int getLayoutId() {
return R.layout.fragment_hand_writing;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
upLoadImagePresenter = new UpLoadImagePresenter(getActivity(), handWriteView);
handWritePrestener = new HandWritePrestener(getActivity(), handWriteView);
View containerView = getActivity().getWindow().getDecorView().findViewById(R.id.task_info_container);
Bitmap bitmap = Bitmap.createBitmap(containerView.getWidth(), containerView.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
canvas.drawColor(Color.WHITE);
containerView.draw(canvas);
// paletteView.setBackgroundColor(Color.WHITE);
paletteView.setBackground(new BitmapDrawable(bitmap));
}
@Override
protected void initialized() {
blackPen = view.findViewById(R.id.black);
redPen = view.findViewById(R.id.red);
yellowPen = view.findViewById(R.id.yellow);
greenPen = view.findViewById(R.id.green);
pinkPen = view.findViewById(R.id.pink);
purplePen = view.findViewById(R.id.purple);
mPenView = view.findViewById(R.id.pen);
mClearView = view.findViewById(R.id.clear);
mEraserView = view.findViewById(R.id.eraser);
mSaveView = view.findViewById(R.id.save);
mQuitView = view.findViewById(R.id.quit);
initProgressDialog();
}
@Override
protected void initListener() {
// paletteView.setCallback(this);
mSaveView.setOnClickListener(this);
mQuitView.setOnClickListener(this);
mPenView.setOnClickListener(this);
mClearView.setOnClickListener(this);
mEraserView.setOnClickListener(this);
redPen.setOnClickListener(this);
yellowPen.setOnClickListener(this);
pinkPen.setOnClickListener(this);
greenPen.setOnClickListener(this);
purplePen.setOnClickListener(this);
blackPen.setOnClickListener(this);
mPenView.setSelected(true);
blackPen.setSelected(true);
}
public static HandWritingFragment newsInstance() {
return new HandWritingFragment();
}
@Override
public void widgetClick(View view) {
switch (view.getId()) {
case R.id.pen:
paletteView.setMode(PaletteView.Mode.DRAW);
paletteView.setPenColor(Color.BLACK);
mPenView.setSelected(true);
mEraserView.setSelected(false);
blackPen.setSelected(true);
greenPen.setSelected(false);
pinkPen.setSelected(false);
yellowPen.setSelected(false);
redPen.setSelected(false);
purplePen.setSelected(false);
break;
case R.id.eraser:
paletteView.setMode(PaletteView.Mode.ERASER);
mEraserView.setSelected(true);
mPenView.setSelected(false);
redPen.setSelected(false);
greenPen.setSelected(false);
pinkPen.setSelected(false);
yellowPen.setSelected(false);
purplePen.setSelected(false);
blackPen.setSelected(false);
break;
case R.id.clear:
paletteView.clear();
break;
case R.id.save:
progressDialog.show();
Bitmap bitmap = paletteView.buildBitmap();
handWritePrestener.saveImageToLocal(bitmap);
break;
case R.id.quit:
popOutBackStack();
break;
case R.id.black:
paletteView.setPenColor(Color.BLACK);
blackPen.setSelected(true);
greenPen.setSelected(false);
pinkPen.setSelected(false);
yellowPen.setSelected(false);
purplePen.setSelected(false);
redPen.setSelected(false);
break;
case R.id.red:
paletteView.setPenColor(Color.RED);
redPen.setSelected(true);
greenPen.setSelected(false);
pinkPen.setSelected(false);
yellowPen.setSelected(false);
purplePen.setSelected(false);
blackPen.setSelected(false);
break;
case R.id.green:
paletteView.setPenColor(Color.GREEN);
greenPen.setSelected(true);
pinkPen.setSelected(false);
yellowPen.setSelected(false);
purplePen.setSelected(false);
redPen.setSelected(false);
blackPen.setSelected(false);
break;
case R.id.yellow:
paletteView.setPenColor(Color.YELLOW);
yellowPen.setSelected(true);
redPen.setSelected(false);
greenPen.setSelected(false);
pinkPen.setSelected(false);
purplePen.setSelected(false);
blackPen.setSelected(false);
break;
case R.id.pink:
paletteView.setPenColor(Color.parseColor("#E771F6"));
pinkPen.setSelected(true);
redPen.setSelected(false);
greenPen.setSelected(false);
yellowPen.setSelected(false);
purplePen.setSelected(false);
blackPen.setSelected(false);
break;
case R.id.purple:
paletteView.setPenColor(Color.parseColor("#6376EB"));
purplePen.setSelected(true);
redPen.setSelected(false);
greenPen.setSelected(false);
yellowPen.setSelected(false);
pinkPen.setSelected(false);
blackPen.setSelected(false);
break;
}
}
private void initProgressDialog() {
progressDialog = new ProgressDialog(getActivity());
progressDialog.setMessage("正在保存,请稍候.....");
progressDialog.setCancelable(false);
}
@Override
public void onUndoRedoStatusChanged() {
}
@Override
public void onDestroy() {
super.onDestroy();
EventBus.getDefault().post(new HandWriteExitEvent());
handWritePrestener.onDestory();
upLoadImagePresenter.onDestory();
}
HandWriteView handWriteView = new HandWriteView() {
@Override
public void onError(String msg) {
if (progressDialog.isShowing())
progressDialog.dismiss();
ToastUtil.showToast(getContext(), msg);
}
@Override
public void onFileUploadSuccess(String fileid, String imgURL) {//上传成功
String teacherId = SPStoreUtil.getString(SystemConstant.sharedName, SystemConstant.teacherID);
String skjlID = SPStoreUtil.getString(SystemConstant.sharedName, SystemConstant.SKJLID);
SXBRequestBean sxbRequestBean = new SXBRequestBean();
sxbRequestBean.setSczid(teacherId);
sxbRequestBean.setSkjlid(skjlID);
sxbRequestBean.setFileid(fileid);
sxbRequestBean.setTplj(imgURL);
handWritePrestener.saveSXB(sxbRequestBean);
}
@Override
public void onSXBSave() {//保存成功
if (progressDialog.isShowing())
progressDialog.dismiss();
ToastUtil.showToast(getActivity(), "图片保存成功");
}
@Override
public void onLocalSaveImageSuccess(HandWriteSaveBean fileBean) {
upLoadImagePresenter.updateFile(
fileBean.getAppDir() + "/" + fileBean.getFileName()
, fileBean.getFileName());
Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
scanIntent.setData(Uri.fromFile(new File(fileBean.getAbsoluteFilePath())));
getContext().sendBroadcast(scanIntent);
}
};
}
效果还是比之前流畅多了。