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

更多相关文章

  1. Android(安卓)support library支持包常用控件介绍(二)
  2. Android(安卓)自定义对话框Dialog
  3. 【转载】 android 圆角圆形图片ShapedImageView不到100行代码
  4. Android(安卓)studio 升级Android(安卓)Support Repository(46.0
  5. 3.0之后在LinearLayout里增加分割线
  6. Android按钮按下时和弹起时的颜色设置
  7. Google VR SDK for Android(1)-introduction(介绍)
  8. Android(安卓)开发入门-活动的基本用法
  9. Android(安卓)无入侵解决按钮重复点击---DoubleClick2.0的使用

随机推荐

  1. Android(安卓)res文件夹下资源定义及使用
  2. JAVA的整型与字符串相互转换 android
  3. android 程序完全退出的有效方法
  4. Android中Xlistview的使用
  5. 自定义Android(安卓)Gradle插件的3种方式
  6. Android(安卓)文字居中方法
  7. Android中的RxBus替换掉EventBus
  8. Android(安卓)ActionBar Tabs学习笔记
  9. Android挂断、接听电话
  10. Android(安卓)推送之Androidpn项目分析(一