編輯:關於Android編程
自定義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() { /* mBitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.RGB_565); mCanvas.setBitmap(mBitmap); // 重新設置畫布,相當於清空畫布 */ initCanvas(); Iterator iter = savePath.iterator(); while (iter.hasNext()) { DrawPath drawPath = iter.next(); mCanvas.drawPath(drawPath.path, drawPath.paint); } invalidate();// 刷新 }
原理:通過onTouch()方法完成,當down時創建path類,並記錄起點,up時獲取重點位置,並將該條路徑存入path實體類中,之後將該path存入一個集合savepath集合中。撤銷時,刪除最上層的path,重做則是刪除所有path即可。
恢復:
/** * 恢復,恢復的核心就是將刪除的那條路徑重新添加到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模式的黑線效果還需要設置在不加背景圖時設置背景資源為0,0即代表顯示默認背景顏色(一般為白色)
橡皮擦相關代碼:
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 ListsavePath; // 保存已刪除Path路徑的集合 private static List 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 (); deletePath = new ArrayList (); } 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 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(); } }
前言本篇博客主要記錄NDK開發之入門小demo,雖說NDK開發包裡面有hellojni的項目,但是博主還是記錄一下學習的過程吧.AS2.2現在對NDK支持的已經很好了,但
微信出了電腦版,在收到視頻後,視頻是保存在哪呢?微信電腦版視頻存在哪裡? 微信電腦版視頻文件夾位置就讓下載吧小編來告訴你吧 1、首先我們打開手機上的微信,
最近想弄一個雙導航功能,查看了許多資料,總算是實現了功能,這邊就算是給自己幾個筆記吧! 先來看看效果 那麼就開始實現了! 底部導航欄我選擇用
本文實例講述了Android編程開發中ListView的常見用法。分享給大家供大家參考,具體如下:一、ListView的使用步驟ListView的使用通常有以下三個要素: