Android自定义组件(三)

本文深入探讨了双缓存技术的工作原理及其在提高绘图性能、展示绘图过程和保存绘图历史方面的作用。通过具体代码示例,展示了如何利用Bitmap和Canvas实现双缓存,包括直线和曲线的绘制,以及在屏幕上绘制矩形的方法。同时,文章还讨论了如何通过Path对象优化绘图效率,以及实现撤销功能的策略。

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()方法进行回收。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值