标签:
自定义view实现涂鸦功能,包括撤销、恢复、重做、保存以及橡皮擦(在风格中实现)功能,小模块包括画笔颜色调整、画笔尺寸调整、画笔类型(包括正常画笔以及橡皮擦功能),之后又陆续实现了画圆、画矩形以及画箭头的功能,这里我们先完成前面的需求
撤销:
/** * 撤销 * 撤销的核心思想就是将画布清空, * 将保存下来的Path路径最后一个移除掉, * 重新将路径画在画布上面。 */ public void undo() { if (savePath != null && savePath.size() > 0) { DrawPath drawPath = savePath.get(savePath.size() - 1); deletePath.add(drawPath); savePath.remove(savePath.size() - 1); redrawOnBitmap(); } }
/** * 重做 www.2cto.com */ public void redo() { if (savePath != null && savePath.size() > 0) { savePath.clear(); redrawOnBitmap(); } }
完成以上两项功能的重要模块
private void redrawOnBitmap() { initCanvas(); Iterator<DrawPath> iter = savePath.iterator(); while (iter.hasNext()) { DrawPath drawPath = iter.next(); mCanvas.drawPath(drawPath.path, drawPath.paint); } invalidate();// 刷新 }
恢复:
/** * 恢复,恢复的核心就是将删除的那条路径重新添加到savapath中重新绘画即可 */ public void recover() { if (deletePath.size() > 0) { //将删除的路径列表中的最后一个,也就是最顶端路径取出(栈),并加入路径保存列表中 DrawPath dp = deletePath.get(deletePath.size() - 1); savePath.add(dp); //将取出的路径重绘在画布上 mCanvas.drawPath(dp.path, dp.paint); //将该路径从删除的路径列表中去除 deletePath.remove(deletePath.size() - 1); invalidate(); } }原理:创建另外一个集合deletaPath用来存放撤销时删除的path,当需要恢复时将该集合中的path重新放入savePath集合中,重新画在画板上,之后savepath中移除该path,invalidate()即可
保存:
//保存到sd卡 public void saveToSDCard() { //获得系统当前时间,并以该时间作为文件名 SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss"); Date curDate = new Date(System.currentTimeMillis());//获取当前时间 String str = formatter.format(curDate) + "paint.png"; File file = new File("sdcard/" + str); FileOutputStream fos = null; try { fos = new FileOutputStream(file); } catch (Exception e) { e.printStackTrace(); } mBitmap.compress(CompressFormat.PNG, 100, fos); //发送Sd卡的就绪广播,要不然在手机图库中不存在 Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED); intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory())); context.sendBroadcast(intent); Log.e("TAG", "图片已保存"); }
样式修改:画板样式,画笔尺寸,画笔颜色
//以下为样式修改内容 //设置画笔样式 public void selectPaintStyle(int which) { if (which == 0) { currentStyle = 1; setPaintStyle(); } //当选择的是橡皮擦时,设置颜色为白色 if (which == 1) { currentStyle = 2; setPaintStyle(); mPaint.setStrokeWidth(20); } } //选择画笔大小 public void selectPaintSize(int which){ int size =Integer.parseInt(this.getResources().getStringArray(R.array.paintsize)[which]); currentSize = size; setPaintStyle(); } //设置画笔颜色 public void selectPaintColor(int which){ currentColor = paintColor[which]; setPaintStyle(); }
//初始化画笔样式 private void setPaintStyle() { mPaint = new Paint(); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.ROUND);// 设置外边缘 mPaint.setStrokeCap(Paint.Cap.ROUND);// 形状 mPaint.setAntiAlias(true); mPaint.setDither(true); if (currentStyle == 1) {//普通画笔功能 mPaint.setStrokeWidth(currentSize); mPaint.setColor(currentColor); } else {//橡皮擦 mPaint.setAlpha(0); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));//这两个方法一起使用才能出现橡皮擦效果 mPaint.setColor(Color.TRANSPARENT); mPaint.setStrokeWidth(50); currentDrawGraphics = DRAW_PATH;//使用橡皮擦时默认用线的方式擦除 } }
private void touch_move(float x, float y) { float dx = Math.abs(x - mX); float dy = Math.abs(mY - y); if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { // 从x1,y1到x2,y2画一条贝塞尔曲线,更平滑(直接用mPath.lineTo也可以) // mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); mPath.lineTo(mX, mY); mCanvas.drawPath(mPath, mPaint); //将一条完整的路径保存下来(相当于入栈操作) savePath.add(dp); mPath.reset(); mPath.moveTo(mX, mY); mX = x; mY = y; } } private void touch_up() { mPath = null;// 重新置空 //mPath.reset(); }2.最终发现只需要设置默认type就能解决该问题
setLayerType(LAYER_TYPE_SOFTWARE,null);//设置默认样式,去除dis-in的黑色方框以及clear模式的黑线效果
橡皮擦相关代码:
if (currentStyle == 1) {//正常画笔 mPaint.setStrokeWidth(currentSize); mPaint.setColor(currentColor); } else {//橡皮擦 mPaint.setAlpha(0); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); mPaint.setColor(Color.TRANSPARENT); mPaint.setStrokeWidth(50); }注意 mPaint.setAlpha(0);mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));两者搭配使用
设置画笔大小的功能:
1)初始化画笔。
2)设置画笔的大小为所选择的大小。
3)用一个变量记住当前画笔的大小,用于在进行其他操作后还保持之前设置的画笔大小。
设置画笔颜色的功能:
1)初始化画笔。
2)设置画笔的颜色为所选择的颜色。
3)用一个变量记住当前画笔的颜色,用于在进行其他操作后还保持之前设置的画笔颜色。
以下为完整代码:
布局:
自定义TuyaView:
package com.banhai.paintboard; /** * Created by zhaopengxiang on 2016/4/6. * View实现涂鸦、撤销以及重做功能 */ import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.net.Uri; import android.os.Environment; import android.util.Log; import android.view.MotionEvent; import android.view.View; import java.io.File; import java.io.FileOutputStream; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; public class TuyaView extends View { private Context context; private Bitmap mBitmap; private Canvas mCanvas; private Path mPath; private Paint mBitmapPaint;// 画布的画笔 private Paint mPaint;// 真实的画笔 private float mX, mY;// 临时点坐标 private static final float TOUCH_TOLERANCE = 4; // 保存Path路径的集合,用List集合来模拟栈 private static List<DrawPath> savePath; // 保存已删除Path路径的集合 private static List<DrawPath> deletePath; // 记录Path路径的对象 private DrawPath dp; private int screenWidth, screenHeight; private int currentColor = Color.RED; private int currentSize = 5; private int currentStyle = 1; private int[] paintColor;//颜色集合 private class DrawPath { public Path path;// 路径 public Paint paint;// 画笔 } public TuyaView(Context context, int w, int h) { super(context); this.context = context; screenWidth = w; screenHeight = h; paintColor = new int[]{ Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW, Color.BLACK, Color.GRAY, Color.CYAN }; setLayerType(LAYER_TYPE_SOFTWARE,null);//设置默认样式,去除dis-in的黑色方框以及clear模式的黑线效果 initCanvas(); savePath = new ArrayList<DrawPath>(); deletePath = new ArrayList<DrawPath>(); } public void initCanvas() { setPaintStyle(); mBitmapPaint = new Paint(Paint.DITHER_FLAG); //画布大小 mBitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888); mBitmap.eraseColor(Color.argb(0, 0, 0, 0)); mCanvas = new Canvas(mBitmap); //所有mCanvas画的东西都被保存在了mBitmap中 mCanvas.drawColor(Color.TRANSPARENT); } //初始化画笔样式 private void setPaintStyle() { mPaint = new Paint(); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeJoin(Paint.Join.ROUND);// 设置外边缘 mPaint.setStrokeCap(Paint.Cap.ROUND);// 形状 mPaint.setAntiAlias(true); mPaint.setDither(true); if (currentStyle == 1) { mPaint.setStrokeWidth(currentSize); mPaint.setColor(currentColor); } else {//橡皮擦 mPaint.setAlpha(0); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN)); mPaint.setColor(Color.TRANSPARENT); mPaint.setStrokeWidth(50); } } @Override public void onDraw(Canvas canvas) { //canvas.drawColor(0xFFAAAAAA); // 将前面已经画过得显示出来 canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint); if (mPath != null) { // 实时的显示 canvas.drawPath(mPath, mPaint); } } private void touch_start(float x, float y) { mPath.moveTo(x, y); mX = x; mY = y; } private void touch_move(float x, float y) { float dx = Math.abs(x - mX); float dy = Math.abs(mY - y); if (dx >= TOUCH_TOLERANCE || dy >= TOUCH_TOLERANCE) { // 从x1,y1到x2,y2画一条贝塞尔曲线,更平滑(直接用mPath.lineTo也可以) mPath.quadTo(mX, mY, (x + mX) / 2, (y + mY) / 2); //mPath.lineTo(mX,mY); mX = x; mY = y; } } private void touch_up() { mPath.lineTo(mX, mY); mCanvas.drawPath(mPath, mPaint); //将一条完整的路径保存下来(相当于入栈操作) savePath.add(dp); mPath = null;// 重新置空 } /** * 撤销 * 撤销的核心思想就是将画布清空, * 将保存下来的Path路径最后一个移除掉, * 重新将路径画在画布上面。 */ public void undo() { if (savePath != null && savePath.size() > 0) { DrawPath drawPath = savePath.get(savePath.size() - 1); deletePath.add(drawPath); savePath.remove(savePath.size() - 1); redrawOnBitmap(); } } /** * 重做 */ public void redo() { if (savePath != null && savePath.size() > 0) { savePath.clear(); redrawOnBitmap(); } } private void redrawOnBitmap() { /*mBitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.RGB_565); mCanvas.setBitmap(mBitmap);// 重新设置画布,相当于清空画布*/ initCanvas(); Iterator<DrawPath> iter = savePath.iterator(); while (iter.hasNext()) { DrawPath drawPath = iter.next(); mCanvas.drawPath(drawPath.path, drawPath.paint); } invalidate();// 刷新 } /** * 恢复,恢复的核心就是将删除的那条路径重新添加到savapath中重新绘画即可 */ public void recover() { if (deletePath.size() > 0) { //将删除的路径列表中的最后一个,也就是最顶端路径取出(栈),并加入路径保存列表中 DrawPath dp = deletePath.get(deletePath.size() - 1); savePath.add(dp); //将取出的路径重绘在画布上 mCanvas.drawPath(dp.path, dp.paint); //将该路径从删除的路径列表中去除 deletePath.remove(deletePath.size() - 1); invalidate(); } } @Override public boolean onTouchEvent(MotionEvent event) { float x = event.getX(); float y = event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 每次down下去重新new一个Path mPath = new Path(); //每一次记录的路径对象是不一样的 dp = new DrawPath(); dp.path = mPath; dp.paint = mPaint; touch_start(x, y); invalidate(); break; case MotionEvent.ACTION_MOVE: touch_move(x, y); invalidate(); break; case MotionEvent.ACTION_UP: touch_up(); invalidate(); break; } return true; } //保存到sd卡 public void saveToSDCard() { //获得系统当前时间,并以该时间作为文件名 SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss"); Date curDate = new Date(System.currentTimeMillis());//获取当前时间 String str = formatter.format(curDate) + "paint.png"; File file = new File("sdcard/" + str); FileOutputStream fos = null; try { fos = new FileOutputStream(file); } catch (Exception e) { e.printStackTrace(); } mBitmap.compress(CompressFormat.PNG, 100, fos); //发送Sd卡的就绪广播,要不然在手机图库中不存在 Intent intent = new Intent(Intent.ACTION_MEDIA_MOUNTED); intent.setData(Uri.fromFile(Environment.getExternalStorageDirectory())); context.sendBroadcast(intent); Log.e("TAG", "图片已保存"); } //以下为样式修改内容 //设置画笔样式 public void selectPaintStyle(int which) { if (which == 0) { currentStyle = 1; setPaintStyle(); } //当选择的是橡皮擦时,设置颜色为白色 if (which == 1) { currentStyle = 2; setPaintStyle(); } } //选择画笔大小 public void selectPaintSize(int which) { //int size = Integer.parseInt(this.getResources().getStringArray(R.array.paintsize)[which]); currentSize = which; setPaintStyle(); } //设置画笔颜色 public void selectPaintColor(int which) { currentColor = paintColor[which]; setPaintStyle(); } }
package com.banhai.paintboard; import android.content.DialogInterface; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.Display; import android.view.View; import android.view.Window; import android.widget.Button; import android.widget.FrameLayout; import android.widget.SeekBar; import android.widget.Toast; public class MainActivity extends AppCompatActivity implements View.OnClickListener{ private FrameLayout frameLayout; private Button btn_undo; private Button btn_redo; private Button btn_save; private Button btn_recover; private TuyaView tuyaView;//自定义涂鸦板 private Button btn_paintcolor; private Button btn_paintsize; private Button btn_paintstyle; private SeekBar sb_size; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.activity_main); initView(); initData(); initListener(); } private void initView() { frameLayout = (FrameLayout) findViewById(R.id.fl_boardcontainer); btn_undo = (Button) findViewById(R.id.btn_last); btn_redo = (Button) findViewById(R.id.btn_redo); btn_save = (Button) findViewById(R.id.btn_savesd); btn_recover = (Button) findViewById(R.id.btn_recover); btn_paintcolor = (Button) findViewById(R.id.btn_paintcolor); btn_paintsize = (Button) findViewById(R.id.btn_paintsize); btn_paintstyle = (Button) findViewById(R.id.btn_paintstyle); sb_size = (SeekBar) findViewById(R.id.sb_size); } private void initData() { //虽然此时获取的是屏幕宽高,但是我们可以通过控制framlayout来实现控制涂鸦板大小 Display defaultDisplay = getWindowManager().getDefaultDisplay(); int screenWidth = defaultDisplay.getWidth(); int screenHeight = defaultDisplay.getHeight(); tuyaView = new TuyaView(this,screenWidth,screenHeight); frameLayout.addView(tuyaView); tuyaView.requestFocus(); tuyaView.selectPaintSize(sb_size.getProgress()); } private void initListener() { btn_undo.setOnClickListener(this); btn_redo.setOnClickListener(this); btn_save.setOnClickListener(this); btn_recover.setOnClickListener(this); btn_paintcolor.setOnClickListener(this); btn_paintsize.setOnClickListener(this); btn_paintstyle.setOnClickListener(this); sb_size.setOnSeekBarChangeListener(new MySeekChangeListener()); } class MySeekChangeListener implements SeekBar.OnSeekBarChangeListener { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { tuyaView.selectPaintSize(seekBar.getProgress()); //Toast.makeText(MainActivity.this,"当前画笔尺寸为"+seekBar.getProgress(),Toast.LENGTH_SHORT ).show(); } @Override public void onStartTrackingTouch(SeekBar seekBar) { tuyaView.selectPaintSize(seekBar.getProgress()); //Toast.makeText(MainActivity.this,"当前画笔尺寸为"+seekBar.getProgress(),Toast.LENGTH_SHORT ).show(); } @Override public void onStopTrackingTouch(SeekBar seekBar) {} } @Override public void onClick(View v) { switch (v.getId()){ case R.id.btn_last://撤销 tuyaView.undo(); break; case R.id.btn_redo://重做 tuyaView.redo(); break; case R.id.btn_recover://恢 tuyaView.recover(); break; case R.id.btn_savesd://保存 tuyaView.saveToSDCard(); break; case R.id.btn_paintcolor: sb_size.setVisibility(View.GONE); showPaintColorDialog(v); break; case R.id.btn_paintsize: sb_size.setVisibility(View.VISIBLE); break; case R.id.btn_paintstyle: sb_size.setVisibility(View.GONE); showMoreDialog(v); break; } } private int select_paint_color_index = 0; private int select_paint_style_index = 0; //private int select_paint_size_index = 0; public void showPaintColorDialog(View parent){ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); alertDialogBuilder.setTitle("选择画笔颜色:"); alertDialogBuilder.setSingleChoiceItems(R.array.paintcolor, select_paint_color_index, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { select_paint_color_index = which; tuyaView.selectPaintColor(which); dialog.dismiss(); } }); alertDialogBuilder.setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); alertDialogBuilder.create().show(); } /* //弹出画笔大小选项对话框 public void showPaintSizeDialog(View parent){ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); alertDialogBuilder.setTitle("选择画笔大小:"); alertDialogBuilder.setSingleChoiceItems(R.array.paintsize, select_paint_size_index, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { select_paint_size_index = which; tuyaView.selectPaintSize(which); dialog.dismiss(); } }); alertDialogBuilder.setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); alertDialogBuilder.create().show(); } */ //弹出选择画笔或橡皮擦的对话框 public void showMoreDialog(View parent){ AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this); alertDialogBuilder.setTitle("选择画笔或橡皮擦:"); alertDialogBuilder.setSingleChoiceItems(R.array.paintstyle, select_paint_style_index, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { select_paint_style_index = which; tuyaView.selectPaintStyle(which); dialog.dismiss(); } }); alertDialogBuilder.setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); alertDialogBuilder.create().show(); } }
标签:
原文地址:http://blog.csdn.net/zhao980919517/article/details/51518044