画渐变圆弧

最近要完成这样的一个效果图:

lizi.png

要求是中间圆环进度条的颜色是渐变的,75%的#FFFFFF渐变到100%,宽度6pt,两端都是圆的,并且有一个20% #000000、8pt的外发光。

看着这要求,很是头疼,还外发光。。

主要的难点有几个:

  1. 背景颜色的渐变
  2. 圆弧的渐变
  3. 让文字画在圆形的中间,图片也画在中间

首先背景颜色的渐变,可以定义一个drawable解决:

<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <gradient
        android:angle="315"
        android:endColor="#00A1F8"
        android:startColor="#84CFFB" />
</shape>

注意的是 angle 属性角度必须是45度的倍数,至于什么度数的渐变方位在哪,那就自己试一下就行,315度就是在左上角。

第二个,圆弧的渐变

  • 让弧线的两端是圆滑的,需要给Pain设置一个属性:
currentPaint.setStrokeCap(Paint.Cap.ROUND); //让弧线两边是圆滑的
  • 线性渐变 LinearGradient

LinearGradient有两个构造方法,分别是:

    /** Create a shader that draws a linear gradient along a line.
        @param x0           The x-coordinate for the start of the gradient line
        @param y0           The y-coordinate for the start of the gradient line
        @param x1           The x-coordinate for the end of the gradient line
        @param y1           The y-coordinate for the end of the gradient line
        @param  colors      The colors to be distributed along the gradient line
        @param  positions   May be null. The relative positions [0..1] of
                            each corresponding color in the colors array. If this is null,
                            the the colors are distributed evenly along the gradient line.
        @param  tile        The Shader tiling mode
    */
    public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[],
            TileMode tile) {
    }

    /** Create a shader that draws a linear gradient along a line.
        @param x0       The x-coordinate for the start of the gradient line
        @param y0       The y-coordinate for the start of the gradient line
        @param x1       The x-coordinate for the end of the gradient line
        @param y1       The y-coordinate for the end of the gradient line
        @param  color0  The color at the start of the gradient line.
        @param  color1  The color at the end of the gradient line.
        @param  tile    The Shader tiling mode
    */
    public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1,
            TileMode tile) {
    }

前面 4 个 参数都是起始坐标和结束坐标,第一个colors数组是沿渐变线分布的颜色,positions数组是 相对位置[0..1]的的颜色阵列中的每个相应的颜色。如果这是空,该颜色是沿着渐变线均匀分布。(其实就是对注释的翻译,我翻译不好)
第二个构造方法后面直接就是开始渐变的颜色和结束渐变的颜色。最后一个参数都传 Shader.TileMode.MIRROR 就好。

其实这里比较想不通的是渐变如果是沿着一条直线还好,怎么才能让它沿着一条弧线来渐变呢,就是说 float x1, float y1 这两个坐标不知道怎么传。

先看怎么画弧线:

mOval = new RectF(
      mStrokeWidth + dp(4),
      mStrokeWidth + dp(4),
      getWidth() - mStrokeWidth - dp(4),
      getHeight() - mStrokeWidth - dp(4)
);

float sweepAngle = ((float) currentValue / (float) totalValue) * 360f;
canvas.drawArc(mOval, mStartAngle, sweepAngle, false, currentPaint);

首先定义一个范围,再画出来。
至于刚刚的问题,参考了一个开源控件的写法,原来这样设置 LinearGradient 的参数就可以达到效果:

mShader = new LinearGradient(
        mOval.left,
        mOval.top,
        mOval.left,
        mOval.bottom,
        mFgColorStart,
        mFgColorEnd,
        Shader.TileMode.MIRROR
);

第三个,让文字画在圆形的中间,图片也画在中间

当你想把文字绘制在中间时,你可能第一时间会想到坐标是这样的:

int textX = getWidth() / 2;
int textY = getHeight() / 2;

可是这样是不行的,你必须要考虑加上文字的宽度和高度才能算正确,
获取文字宽度的方法:

textPaint.measureText(text + ""); //测量文字,得到宽度

因为文字也是数字,也是可以测量的。

得到文字高度,必须理解基准线这东西,这里有个文章介绍。

再贴个图:

120628055573351.png

可以看到,得到高度,就相当于 ascent 减去 descent:

float textHeight = textPaint.ascent() + textPaint.descent(); //根据基准线得到文字的高

所有这样的坐标才能画在正中间:

int textX = getWidth() / 2;
int textY = getHeight() / 2;

textX = textX - textWidth / 2;
textY = textY - textHeight / 2;

图片的话,Bitmap 都有相应的 get 方法,就不说了。

==================分割线=2016.6.3==========================

更正一个错误,沿着弧线渐变用线性渐变是不行的,如果用以上方法它会有一种效果就是从浅到深后又会由深到浅。

正确的方法是使用梯度渐变 SweepGradient 和 Matrix结合起来才有效果。
首先先看下 SweepGradient 的大致效果如下:

1344993412_1866.png

这样的效果就不会出现线性渐变的问题了。

代码如下:

private void init() {
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setStrokeWidth(50);  
    mPaint.setStrokeCap(Paint.Cap.ROUND);
    mPaint.setColor(Color.WHITE);
    mMatrix = new Matrix();
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    int x = getWidth() / 2;
    int y = getHeight() / 2;
    float startAngle = 270;
    float sweepAngle = 340;

    mMatrix.setRotate(-150, x, y);
    mShader = new SweepGradient(startAngle, sweepAngle, new int[]{0x4dffffff, 0xffffffff}, null);
    mShader.setLocalMatrix(mMatrix);

    mPaint.setShader(mShader);
    RectF mOval = new RectF(25, 25, getWidth() - 25, getHeight() - 25);

    canvas.drawArc(mOval, startAngle, sweepAngle, false, mPaint);
}

其中,核心代码是:

mMatrix.setRotate(-150, x, y);
mShader.setLocalMatrix(mMatrix);

没有这两句,怎么写都是白搭。

其中 x 和 y 是圆心坐标,-150的意思是 将扫描起始的地方逆时针选择90°后,圆角画笔的地方还有半个圆没有遮盖。
因为我是从12点方向开始画的,而扫描开始的方向是3点钟,所以我填的是 -90 ,剩下那 -60 度是微调,这个需要按实际情况而定,看下这两个图或自己试一下就知道什么意思了:
填 -90 度的时候效果:

a.png

加上 -60 度的微调后的效果:

b.png

这样,应该能够明白什么意思了。

不过有一个bug,当结束角度接近360度的时候,会显示成这样:


c.png

暂时没有解决办法,哪位大神看到有办法的,请支招。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容