如何在画布中绘制具有径向渐变的圆?

时间:2013-10-17 14:06:22

标签: android android-canvas android-view

我创建了一个圆形按钮,可以在调用函数时更改颜色。我想要的是创建另一个,创建相同的圆形按钮,但径向渐变从中间开始选择颜色,当你离开圆圈时,它会变为透明。

我使用How to set gradient style to paint object?发布的代码创建了一个类似的代码但是没有用。

我试过的代码就是这个例子:

mPaint.setShader(new RadialGradient(0, 0, height/3, Color.BLACK, Color.TRANSPARENT, Shader.TileMode.MIRROR));

以下类是我为圆形按钮创建的类。

public class ColorGradientCircleButton extends View{

private Paint mPaint;
private Paint   mBitmapPaint;
private Bitmap  mBitmap;
private Canvas  mCanvas;
private int width, height;

public ColorGradientCircleButton(Context context) {
    super(context);
    init();
}
public ColorGradientCircleButton(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
}
public ColorGradientCircleButton(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
}
private void init() {
    mPaint = new Paint();
    mPaint.setColor(Color.BLACK);
    mPaint.setStrokeWidth(1);
    mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    mBitmapPaint = new Paint(Paint.DITHER_FLAG);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    width = w;
    height = h;
    mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    mCanvas = new Canvas(mBitmap);
    mCanvas.drawCircle(w/2, h/2, h/3, mPaint);
}
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
}
public void changeColor(int color){
    mPaint.setColor(color);
    mCanvas.drawCircle(width/2, height/2, height/3, mPaint);
    invalidate();
}
}

1 个答案:

答案 0 :(得分:20)

我们应该将其迁移到答案框。

OP已经基本上得到了它 - 事实上OP的修订版gist非常出色。

关于问题中第一次尝试的一些一般性提示:

1)在protected void onSizeChanged(int w, int h, int oldw, int oldh)

  • width = w;没有理由要求您在需要时拨打getWidth()。之所以这样做是因为View的内部宽度设置在onMeasure之后很晚。因此,onDraw可能是您下次想要最新版本的时候,因此请在那里使用getter。
  • mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);。创建位图是一项昂贵且占用大量内存的操作。除非您想将位图写入文件,或将其发送到BitmapDrawableImageView或其他内容,否则您无需执行此操作。特别是使用android的graphics库将效果绘制到UI上。
  • mCanvas = new Canvas(mBitmap);然后在新画布上进行绘制操作。这是永远不需要的。然而,我在许多代码库和尝试中都看到过它(不起作用)。我认为这是一个旧的堆栈溢出帖子的错,让人们这样做,以便他们可以在自定义视图上转换画布,而不会影响画布的其余部分。顺便提一下,如果您需要,请改用.restore().save()。如果您看到new Canvas可疑

2)onDraw(...)

  • ,您需要避免在onDraw中执行操作,例如创建对象或任何繁重的处理。 但是你仍然需要在onDraw onDraw中完成所需的事情!
  • 所以,您只需根据docs使用参数调用canvas.drawCircle(float cx, float cy, float radius, Paint paint)
  • 对于onDraw来说,这真的不是那么有罪。 如果您担心过多地调用,如果您的整个按钮在屏幕上设置了动画效果,则需要使用以后的API版本中提供的hardware acceleration,详见一篇名为Optimizing the View的文章;如果您使用大量自定义绘制的视图,则非常有用。

3)那令人讨厌的径向渐变。你遇到的下一个问题是你在init方法中正确地创建了你的绘画,以便创建对象。但是非常正确的是它会IllegalArgumentException编辑(我认为)在你身上,因为在那个阶段,视图的getHeight()是0.你尝试传递小的像素值 - 除非你了解屏幕尺寸的一些魔力。

这不是你的问题,而是Android设计模式核心的烦人视图周期。虽然修复很简单:只需在onMeasure调用之后使用视图绘制过程的后续部分来设置绘制过滤器。

但是有一些问题需要做到这一点,即有时候,令人烦恼的是,onDraw会在您预期的时间点之前被调用。结果将是您的paint为null并且您将无法获得所需的行为。

我发现一个更强大的解决方案就是在onDraw中进行一次厚颜无耻的淘气小检查,然后只需一次在那里构建paint对象。它并不是严格来说是最优的,但考虑到Paint对象与Android的图形原生层相结合的复杂方式,而不是试图跨越许多频繁调用的地方的绘画配置和构造。它使得代码更加清晰。

这看起来像是(修改你的要点):

        @Override
        protected void onDraw(final Canvas canvas) {
            super.onDraw(canvas);
            if (mPaint == null) {
                mPaint = new Paint();
                mPaint.setColor(Color.BLACK);
                mPaint.setStrokeWidth(1);
                mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
                mPaint.setShader(new RadialGradient(getWidth() / 2, getHeight() / 2,
                        getHeight() / 3, Color.TRANSPARENT, Color.BLACK, TileMode.MIRROR));
            }
            width = getWidth();
            height = getHeight();
            canvas.drawCircle(width / 2, height / 2, height / 3, mPaint);
        }

所以请注意一些变化 - 我想从你的描述中你想要在参数中交换两种颜色,也不要忘记在你的视图中将渐变的中心居中:width/2和{{1参数。

祝你好运!