Android自定義組件(三)

1.聊一聊雙緩存技術

爲什麼叫“雙緩存”,其實就是有兩個繪圖區,一個是Bitmap的Canvas,另一個則是當前View的Canvas。

使用雙緩存的意義:

1)提高繪圖的性能

2)可以在屏幕上展示繪圖的過程

3)保存繪圖歷史

(1)下面是使用雙緩存來繪製圖形的代碼

    private Paint paint;

    /**
     * 上一個點的座標
     */
    private int preX, preY;
    /**
     * 當前點的座標
     */
    private int currentX, currentY;
    /**
     * Bitmap 緩存區
     */
    private Bitmap bitmapBuffer;
    private Canvas bitmapCanvas;

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLACK);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(5);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (bitmapBuffer == null) {
            int width = getMeasuredWidth();//獲取 View 的寬度
            int height = getMeasuredHeight();    //獲取 View 的高度
            //新建 Bitmap 對象
            bitmapBuffer = Bitmap.createBitmap(width, height,
                    Bitmap.Config.ARGB_8888);
            bitmapCanvas = new Canvas(bitmapBuffer);
        }
    }

    //    public void run() {
//        new Timer().schedule(new TimerTask() {
//            @Override
//            public void run() {
//                postInvalidate();
//            }
//        }, 0, 1000);
//    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //將 Bitmap 中的內容繪製在 View 上
        canvas.drawBitmap(bitmapBuffer, 0, 0, null);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
//              手指按下,記錄第一個點的座標
                preX = x;
                preY = y;
                break;
            case MotionEvent.ACTION_MOVE:
//              手指移動,記錄當前點的座標
                currentX = x;
                currentY = y;
                bitmapCanvas.drawLine(preX, preY, currentX, currentY, paint);
                this.invalidate();
//              當前點的座標成爲下一個點的起始座標
                preX = currentX;
                preY = currentY;
                break;
            case MotionEvent.ACTION_UP:
                invalidate();
                break;
            default:
                break;
        }
        return true;
    }
}

首先我們創建了一個bitmapBuffer的Bitmap對象,爲了在上面繪圖,我們又創建了一個與之關聯的Canvas對象bitmapCanvas。

在創建Bitmap時需要考慮他的大小,但是此時myView尚未創建,所以重寫了onSizeChanged(),該方法在組件創建後且大小發生改變時回調(在第一次顯示時肯定會調用)。

在上面的代碼中我們使用的是直接繪製直線的方法,但是更好的做法是通過Path來繪圖。

1)可以保存實時的繪圖座標。

2)可以用來繪製更加複雜的圖形。

3)繪圖效率更高。

public class MyView extends View {

    private Path path;
    private int preX, preY;
    private Paint paint;
    private Bitmap bitmapBuffer;
    private Canvas bitmapCanvas;

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.RED);
        paint.setStrokeWidth(10);
        path = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(bitmapBuffer, 0, 0, null);
        canvas.drawPath(path, paint);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (bitmapBuffer == null) {
            int width = getMeasuredWidth();
            int height = getMeasuredHeight();
            bitmapBuffer = Bitmap.createBitmap(width, height,
                    Bitmap.Config.ARGB_8888);
            bitmapCanvas = new Canvas(bitmapBuffer);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                path.reset();
                preX = x;
                preY = y;
                path.moveTo(x, y);
                break;
            case MotionEvent.ACTION_MOVE:
                path.quadTo(preX, preY, x, y);
                invalidate();
                preX = x;
                preY = y;
                break;
            case MotionEvent.ACTION_UP:
                bitmapCanvas.drawPath(path, paint);
                invalidate();
                break;
            default:
                break;
        }
        return true;
    }
}

在這裏我們繪製曲線的時候我們採用的是二階貝塞爾曲線,其中貝塞爾曲線的控制點和起點爲同一個點。

(2)在屏幕上繪製矩形

public class MyView extends View {

    private int firstX, firstY;//暫時存儲手指落下的位置
    private Path path;
    private Paint paint;
    private Bitmap bitmapBuffer;
    private Canvas bitmapCanvas;

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.RED);
        paint.setStrokeWidth(10);
        path = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawBitmap(bitmapBuffer, 0, 0, null);
        canvas.drawPath(path, paint);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (bitmapBuffer == null) {
            int width = getMeasuredWidth();
            int height = getMeasuredHeight();
            bitmapBuffer = Bitmap.createBitmap(width, height,
                    Bitmap.Config.ARGB_8888);
            bitmapCanvas = new Canvas(bitmapBuffer);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                firstX = x;
                firstY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                //繪製矩形時,要先清除前一次的結果
                path.reset();
                if (firstX < x && firstY < y) {
                    path.addRect(firstX, firstY, x, y, Path.Direction.CCW);
                } else if (firstX > x && firstY > y) {
                    path.addRect(x, y, firstX, firstY, Path.Direction.CCW);
                } else if (firstX > x && firstY < y) {
                    path.addRect(x, firstY, firstX, y, Path.Direction.CCW);
                } else if (firstX < x && firstY > y) {
                    path.addRect(firstX, y, x, firstY, Path.Direction.CCW);
                }
                invalidate();
                break;
            case MotionEvent.ACTION_UP:
                bitmapCanvas.drawPath(path, paint);
                invalidate();
                break;
            default:
                break;
        }
        return true;
    }
}

如果要實現撤銷刪除的功能,可以將每次繪製後的圖形暫存到List中,這樣每次繪製都需要遍歷整個List從而繪製出全部的圖形。

由於Bitmap十分佔用系統資源,撤銷後需要及時調用Bitmap.recycle()方法進行回收。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章