畫漸變圓弧

最近要完成這樣的一個效果圖:

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

暫時沒有解決辦法,哪位大神看到有辦法的,請支招。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 本文轉載自:http://southpeak.github.io/2014/12/10/quartz2d-8/ Q...
    idiot_lin閱讀 891評論 0 3
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,019評論 25 708
  • 當春天來臨,風帶來復蘇的訊息, 要多么努力才可以破土而出, 要多么執著才可以不言放棄, 等一個冬季為了見到春天的陽...
    沿途晴朗閱讀 285評論 0 0
  • 一個成長最好的進攻武器是智商,最好的防守武器是自己的道德底線,但大多數人正好相反,用道德攻擊對手,用愚蠢來捍衛自己...
    讀圖筆記丶閱讀 417評論 0 0