如何在Android中为画布制作自定义画笔?

37
在我的画布应用程序中,我想使用像附图中的笔刷一样的自定义笔刷。请有人快速帮助我,如何制作像附图中的自定义笔刷?
在我的应用程序中,我使用以下代码制作了点线:
 mPaint.setPathEffect(new DashPathEffect(new float[] { 8, 8 }, 0));

使用以下代码实现模糊和浮雕效果:

 mEmboss = new EmbossMaskFilter(new float[] { 1, 1, 1 }, 0.4f, 6, 3.5f);

 mBlur = new BlurMaskFilter(8, BlurMaskFilter.Blur.NORMAL);

在这里输入图片描述

3个回答

22

正如你可以清楚地看到的那样,没有平凡的着色器效果/矩形/圆形可以实现这一点。使用图像/位图。

因此,只需使用canvas.drawBitmap重复绘制位图。当手指移动时,您一遍又一遍地绘制相同的位图。

要添加自定义颜色,可以添加简单的过滤器。

一个例子

public class CanvasBrushDrawing extends View {
    private Bitmap mBitmapBrush;
    private Vector2 mBitmapBrushDimensions;

    private List<Vector2> mPositions = new ArrayList<Vector2>(100);

    private static final class Vector2 {
        public Vector2(float x, float y) {
            this.x = x;
            this.y = y;
        }

        public final float x;
        public final float y;
    }

    public CanvasBrushDrawing(Context context) {
        super(context);

// load your brush here
        mBitmapBrush = BitmapFactory.decodeResource(context.getResources(), R.drawable.splatter_brush);
        mBitmapBrushDimensions = new Vector2(mBitmapBrush.getWidth(), mBitmapBrush.getHeight());

        setBackgroundColor(0xffffffff);
    }

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

        for (Vector2 pos : mPositions) {
            canvas.drawBitmap(mBitmapBrush, pos.x, pos.y, null);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {

        int action = event.getAction();
        switch (action) {
        case MotionEvent.ACTION_MOVE:
            final float posX = event.getX();
            final float posY = event.getY();
            mPositions.add(new Vector2(posX - mBitmapBrushDimensions.x / 2, posY - mBitmapBrushDimensions.y / 2));
            invalidate();
        }

        return true;
    }
}

你的答案帮助我将位图作为画笔绘制,但当我更改颜色过滤器的颜色时,它会应用于整条线。 - Hardik
@Hardik,你找到解决这个问题的方法了吗? - Muhammad Umair Shafique
从OnDraw方法中删除for循环,只保留canvas.drawBitmap()方法,在MOVE操作中保留mCanvas.drawBitmap(mBitmapBrush, touchX, touchY, mPaint);和invalidate()。 - Hardik
@Hardik,我现在有点困惑,在OnDraw()方法和ACTION_MOVE事件中,canvas.drawBitmap()的x、y坐标应该是什么? - Falling Into Infinity
@poitroae,这将导致严重的内存问题。由于您在画布中应用了大量位图,这将使UI更加沉重。假设我已经绘制了数百条线条,每条线条都包含大量位图画刷。 - Prokash Sarkar
显示剩余3条评论

7

虽然为时已晚,但我想分享一些内容。这可能会对某些人有所帮助。以下链接讨论了各种画笔技术,并提供了HTML画布的JavaScript代码。你只需要将JavaScript代码转换成你期望的代码即可。将JavaScript Canvas代码转换为Android Canvas代码非常简单。

探索Canvas绘图技术

我已经将“多条线”技术转换为适用于Android的Java代码;你可以查看下面的Android视图代码。

public class MultipleLines extends View {

    private Bitmap bitmap;
    private Canvas canvas;

    private Paint mPaint;

    public MultipleLines(Context context) {
        super(context);
        init();
    }

    private void init(){
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(0xFFFF0000);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(1);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
        canvas = new Canvas(bitmap);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float x = event.getX();
        float y = event.getY();

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                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;
    }

    private boolean isDrawing;
    private List<PointF> points = new ArrayList<>();

    private void touch_start(float touchX, float touchY) {
        isDrawing = true;
        points.add(new PointF(touchX, touchY));

        canvas.save();
    }
    private void touch_move(float touchX, float touchY) {
        if (!isDrawing) return;

        canvas.drawColor(Color.TRANSPARENT);

        points.add(new PointF(touchX, touchY));

        stroke(offsetPoints(-10));
        stroke(offsetPoints(-5));
        stroke(points);
        stroke(offsetPoints(5));
        stroke(offsetPoints(10));
    }

    private void touch_up() {
        isDrawing = false;
        points.clear();
        canvas.restore();
    }

    private List<PointF> offsetPoints(float val) {
        List<PointF> offsetPoints = new ArrayList<>();
        for (int i = 0; i < points.size(); i++) {
            PointF point = points.get(i);
            offsetPoints.add(new PointF(point.x + val, point.y + val));
        }
        return offsetPoints;
    }

    private void stroke(List<PointF> points) {
        PointF p1 = points.get(0);
        PointF p2 = points.get(1);

        Path path = new Path();
        path.moveTo(p1.x, p1.y);

        for (int i = 1; i < points.size(); i++) {
            // we pick the point between pi+1 & pi+2 as the
            // end point and p1 as our control point
            PointF midPoint = midPointBtw(p1, p2);
            path.quadTo(p1.x, p1.y, midPoint.x, midPoint.y);
            p1 = points.get(i);
            if(i+1 < points.size()) p2 = points.get(i+1);
        }
        // Draw last line as a straight line while
        // we wait for the next point to be able to calculate
        // the bezier control point
        path.lineTo(p1.x, p1.y);

        canvas.drawPath(path,mPaint);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.drawColor(Color.WHITE);
        canvas.drawBitmap(bitmap, 0, 0, null);
    }

    private PointF midPointBtw(PointF p1, PointF p2) {
        return new PointF(p1.x + (p2.x - p1.x) / 2.0f, p1.y + (p2.y - p1.y) / 2.0f);
    }
}

顺便说一下,这是真正的答案,但很棘手,请帮我再提供更多的解决方法好吗? - Mubashir Murtaza
这个链接非常有帮助,它讨论了几种常用笔刷的实现。 - L. Swifter

0
我正在将图像转换为SVG,然后创建一个矢量图像,并在其中使用路径数据,以便我可以直接使用drawPath进行绘制,但是如果你有一个快速的手指滑动,你需要单独处理丢失的路径。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接