2025-07-12

Java代碼 
import java.io.File; 
import java.io.FileNotFoundException; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.util.ArrayList; 
import java.util.Iterator; 
import java.util.List; 
 
import android.content.Context; 
import android.graphics.Bitmap; 
import android.graphics.Canvas; 
import android.graphics.Paint; 
import android.graphics.Path; 
import android.graphics.Bitmap.CompressFormat; 
import android.os.Environment; 
import android.view.MotionEvent; 
import android.view.View; 
 
/**
 * View實現塗鴉、撤銷以及重做功能
 */ 
 
public class TuyaView extends View { 
 
    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 DrawPath dp; 
 
    private int screenWidth, screenHeight; 
 
    private class DrawPath { 
        public Path path;// 路徑 
        public Paint paint;// 畫筆 
    } 
 
    public TuyaView(Context context, int w, int h) { 
        super(context); 
        screenWidth = w; 
        screenHeight = h; 
 
        mBitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888); 
        // 保存一次一次繪制出來的圖形 
        mCanvas = new Canvas(mBitmap); 
 
        mBitmapPaint = new Paint(Paint.DITHER_FLAG); 
        mPaint = new Paint(); 
        mPaint.setAntiAlias(true); 
        mPaint.setStyle(Paint.Style.STROKE); 
        mPaint.setStrokeJoin(Paint.Join.ROUND);// 設置外邊緣 
        mPaint.setStrokeCap(Paint.Cap.ROUND);// 形狀 
        mPaint.setStrokeWidth(5);// 畫筆寬度 
 
        savePath = new ArrayList<DrawPath>(); 
    } 
 
    @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); 
            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) { 
            savePath.remove(savePath.size() – 1); 
            redrawOnBitmap(); 
        } 
    } 
    /**
     * 重做 www.aiwalls.com
     */ 
    public void redo(){ 
        if (savePath != null && savePath.size() > 0) { 
            savePath.clear(); 
            redrawOnBitmap(); 
        } 
    } 
     
    private void redrawOnBitmap(){ 
        mBitmap = Bitmap.createBitmap(screenWidth, screenHeight, 
                Bitmap.Config.ARGB_8888); 
        mCanvas.setBitmap(mBitmap);// 重新設置畫佈,相當於清空畫佈  
        Iterator<DrawPath> iter = savePath.iterator(); 
        while (iter.hasNext()) { 
            DrawPath drawPath = iter.next(); 
            mCanvas.drawPath(drawPath.path, drawPath.paint); 
        } 
        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; 
    } 
 
    public void saveToSDCard(){ 
        String fileUrl = Environment.getExternalStorageDirectory() 
                .toString() + "/android/data/test.png"; 
        try { 
            FileOutputStream fos = new FileOutputStream(new File(fileUrl)); 
            mBitmap.compress(CompressFormat.PNG, 100, fos); 
            fos.flush(); 
            fos.close(); 
        } catch (FileNotFoundException e) { 
            e.printStackTrace(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 

Java代碼 
import android.app.Activity; 
import android.os.Bundle; 
import android.util.DisplayMetrics; 
import android.util.Log; 
import android.view.KeyEvent; 
 
public class TuyaActivity extends Activity { 
 
    private TuyaView tuyaView = null; 
 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
 
        DisplayMetrics dm = new DisplayMetrics(); 
        getWindowManager().getDefaultDisplay().getMetrics(dm); 
 
        tuyaView = new TuyaView(this, dm.widthPixels, dm.heightPixels); 
        setContentView(tuyaView); 
    } 
 
    @Override 
    public boolean onKeyDown(int keyCode, KeyEvent event) { 
        if (keyCode == KeyEvent.KEYCODE_BACK) {// 返回鍵 
            tuyaView.undo(); 
            return true; 
        }else if(keyCode == KeyEvent.KEYCODE_MENU){//MENU 
            tuyaView.redo(); 
            return true; 
        } 
        return super.onKeyDown(keyCode, event); 
    } 
 

摘自 與時俱進

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *