Android(安卓)实现一个简单的画板功能
16lz
2021-01-26
Android炫酷画板 下载地址:源码1 源码2
一.效果图:
二.快速实现:
a.支持撤销(undo)
b.支持反撤销(redo)
c.支持橡皮擦(eraser)
d.支持清除功能(clear)
e.支持保存为图像(save)
1.主函数代码:
import android.app.ProgressDialog;import android.content.Context;import android.content.Intent;import android.graphics.Bitmap;import android.net.Uri;import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.os.Message;import android.support.v7.app.AppCompatActivity;import android.view.Menu;import android.view.MenuItem;import android.view.View;import android.widget.Toast;import java.io.File;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;public class MainActivity extends AppCompatActivity implements View.OnClickListener, PaletteView.Callback,Handler.Callback { private View mUndoView; private View mRedoView; private View mPenView; private View mEraserView; private View mClearView; private PaletteView mPaletteView; private ProgressDialog mSaveProgressDlg; private static final int MSG_SAVE_SUCCESS = 1; private static final int MSG_SAVE_FAILED = 2; private Handler mHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mPaletteView = (PaletteView) findViewById(R.id.palette); mPaletteView.setCallback(this); mUndoView = findViewById(R.id.undo); mRedoView = findViewById(R.id.redo); mPenView = findViewById(R.id.pen); mPenView.setSelected(true); mEraserView = findViewById(R.id.eraser); mClearView = findViewById(R.id.clear); mUndoView.setOnClickListener(this); mRedoView.setOnClickListener(this); mPenView.setOnClickListener(this); mEraserView.setOnClickListener(this); mClearView.setOnClickListener(this); mUndoView.setEnabled(false); mRedoView.setEnabled(false); mHandler = new Handler(this); } @Override protected void onDestroy() { super.onDestroy(); mHandler.removeMessages(MSG_SAVE_FAILED); mHandler.removeMessages(MSG_SAVE_SUCCESS); } private void initSaveProgressDlg(){ mSaveProgressDlg = new ProgressDialog(this); mSaveProgressDlg.setMessage("正在保存,请稍候..."); mSaveProgressDlg.setCancelable(false); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu, menu); return true; } @Override public boolean handleMessage(Message msg) { switch (msg.what){ case MSG_SAVE_FAILED: mSaveProgressDlg.dismiss(); Toast.makeText(this,"保存失败",Toast.LENGTH_SHORT).show(); break; case MSG_SAVE_SUCCESS: mSaveProgressDlg.dismiss(); Toast.makeText(this,"画板已保存",Toast.LENGTH_SHORT).show(); break; } return true; } private static void scanFile(Context context, String filePath) { Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); scanIntent.setData(Uri.fromFile(new File(filePath))); context.sendBroadcast(scanIntent); } private static String saveImage(Bitmap bmp, int quality) { if (bmp == null) { return null; } File appDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); if (appDir == null) { return null; } String fileName = System.currentTimeMillis() + ".jpg"; File file = new File(appDir, fileName); FileOutputStream fos = null; try { fos = new FileOutputStream(file); bmp.compress(Bitmap.CompressFormat.JPEG, quality, fos); fos.flush(); return file.getAbsolutePath(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } return null; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.save: if(mSaveProgressDlg==null){ initSaveProgressDlg(); } mSaveProgressDlg.show(); new Thread(new Runnable() { @Override public void run() { Bitmap bm = mPaletteView.buildBitmap(); String savedFile = saveImage(bm, 100); if (savedFile != null) { scanFile(MainActivity.this, savedFile); mHandler.obtainMessage(MSG_SAVE_SUCCESS).sendToTarget(); }else{ mHandler.obtainMessage(MSG_SAVE_FAILED).sendToTarget(); } } }).start(); break; } return true; } @Override public void onUndoRedoStatusChanged() { mUndoView.setEnabled(mPaletteView.canUndo()); mRedoView.setEnabled(mPaletteView.canRedo()); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.undo: mPaletteView.undo(); break; case R.id.redo: mPaletteView.redo(); break; case R.id.pen: v.setSelected(true); mEraserView.setSelected(false); mPaletteView.setMode(PaletteView.Mode.DRAW); break; case R.id.eraser: v.setSelected(true); mPenView.setSelected(false); mPaletteView.setMode(PaletteView.Mode.ERASER); break; case R.id.clear: mPaletteView.clear(); break; } }}
2.主要的自定义代码:
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.graphics.PorterDuffXfermode;import android.graphics.Xfermode;import android.support.annotation.Nullable;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import java.util.ArrayList;import java.util.List;/** * 自定义画板 */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 mDrawingList; private List 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(0XFF000000);//设置画笔的颜色 mPaint.setColor(Color.BLUE); 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: //这里终点设为两点的中心点的目的在于使绘制的曲线更平滑,如果终点直接设置为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; }}
3.布局:
<?xml version="1.0" encoding="utf-8"?>
4.按钮背景状态:(从左到右)
第一个按钮和第二个按钮背景一样
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
5.工具类:
package com.beyondsw.palette;import android.content.res.Resources;import android.util.DisplayMetrics;import android.util.TypedValue;/** * 工具类转换 */public class DimenUtils { private static final Resources sResource = Resources.getSystem(); public static float dp2px(float dp) { DisplayMetrics dm = sResource.getDisplayMetrics(); return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, dm); } public static int dp2pxInt(float dp) { return (int) dp2px( dp); } public static float sp2px(float sp) { DisplayMetrics dm = sResource.getDisplayMetrics(); return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sp, dm); } public static int sp2pxInt(float sp) { return (int) sp2px(sp); }}
参考:https://github.com/wensefu/android-palette
更多相关文章
- Android(安卓)support library支持包常用控件介绍(二)
- Android(安卓)自定义对话框Dialog
- 【转载】 android 圆角圆形图片ShapedImageView不到100行代码
- Android(安卓)studio 升级Android(安卓)Support Repository(46.0
- 3.0之后在LinearLayout里增加分割线
- Android按钮按下时和弹起时的颜色设置
- Google VR SDK for Android(1)-introduction(介绍)
- Android(安卓)开发入门-活动的基本用法
- Android(安卓)无入侵解决按钮重复点击---DoubleClick2.0的使用