精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Android自定義View之圓形進度條總結(jié)

移動開發(fā) Android
最近擼了一個圓形進度條的開源項目,算是第一次完完整整的使用自定義 View 。在此對項目開發(fā)思路做個小結(jié),歡迎大家 Star 和 Fork。

最近擼了一個圓形進度條的開源項目,算是***次完完整整的使用自定義 View 。在此對項目開發(fā)思路做個小結(jié),歡迎大家 Star 和 Fork。

該項目總共實現(xiàn)了三種圓形進度條效果

  1. CircleProgress:圓形進度條,可以實現(xiàn)仿 QQ 健康計步器的效果,支持配置進度條背景色、寬度、起始角度,支持進度條漸變
  2. DialProgress:類似 CircleProgress,但是支持刻度
  3. WaveProgress:實現(xiàn)了水波紋效果的圓形進度條,不支持漸變和起始角度配置,如需此功能可參考 CircleProgress 自行實現(xiàn)。

先上效果圖,有圖才好說。

CircleProgress 效果圖   

CircleProgress 效果圖 

DialProgress 和 WaveProgress 效果圖  

DialProgress 和 WaveProgress 效果圖 

恩,那么接下來,就來講講怎么實現(xiàn)以上自定義進度條的效果。

圓形進度條

圓形進度條是***個實現(xiàn)的進度條效果,用了我大半天的時間,實現(xiàn)起來并不復(fù)雜。

其思路主要可以分為以下幾步:

  1. View 的測量
  2. 計算繪制 View 所需參數(shù)
  3. 圓弧的繪制及漸變的實現(xiàn)
  4. 文字的繪制
  5. 動畫效果的實現(xiàn)

首先,我們要測量出所繪制 View 的大小,即重寫 onMeasure() 方法,代碼如下:

  1. @Override 
  2.  
  3. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  4.  
  5.    super.onMeasure(widthMeasureSpec, heightMeasureSpec); 
  6.  
  7.    setMeasuredDimension(MiscUtil.measure(widthMeasureSpec, mDefaultSize), 
  8.  
  9.            MiscUtil.measure(heightMeasureSpec, mDefaultSize)); 
  10.  
  11.  

由于其他兩個進度條類都需要實現(xiàn) View 的測量,這里對代碼進行了封裝:

  1. /** 
  2.  
  3. * 測量 View 
  4.  
  5.  
  6. * @param measureSpec 
  7.  
  8. * @param defaultSize View 的默認大小 
  9.  
  10. * @return 測量出來的 View 大小 
  11.  
  12. */ 
  13.  
  14. public static int measure(int measureSpec, int defaultSize) { 
  15.  
  16.    int result = defaultSize; 
  17.  
  18.    int specMode = View.MeasureSpec.getMode(measureSpec); 
  19.  
  20.    int specSize = View.MeasureSpec.getSize(measureSpec); 
  21.  
  22.   
  23.  
  24.    if (specMode == View.MeasureSpec.EXACTLY) { 
  25.  
  26.        result = specSize; 
  27.  
  28.    } else if (specMode == View.MeasureSpec.AT_MOST) { 
  29.  
  30.        result = Math.min(result, specSize); 
  31.  
  32.    } 
  33.  
  34.    return result; 
  35.  
  36.  

關(guān)于 View 測量可以看下這篇博客 Android 自定義View 中的onMeasure的用法

接下來,在 onSizeChanged() 中計算繪制圓及文字所需的參數(shù),考慮到屏幕旋轉(zhuǎn)的情況,故未直接在 onMeasure() 方法中直接計算。這里以下面草圖來講解繪制計算過程中的注意事項,圖丑,勿怪~   

 

圖中,外面藍色矩形為 View,里面黑色矩形為圓的外接矩形,藍色矩形和黑色矩形中間空白的地方為 View 的內(nèi)邊距(padding)。兩個藍色的圓其實是一個圓,代表圓的粗細,這是因為 Android 在繪制圓或者圓弧的時候是圓的邊寬的中心與外接矩形相交,所以在計算的時候要考慮到內(nèi)邊距(padding) 和 圓與外接矩形的相交。

默認不考慮圓弧的寬度,繪制出來的效果如下:   

 

 

  1. @Override 
  2.  
  3. protected void onSizeChanged(int w, int h, int oldw, int oldh) { 
  4.  
  5.    super.onSizeChanged(w, h, oldw, oldh); 
  6.  
  7.    Log.d(TAG, "onSizeChanged: w = " + w + "; h = " + h + "; oldw = " + oldw + "; oldh = " + oldh); 
  8.  
  9.    //求圓弧和背景圓弧的***寬度 
  10.  
  11.    float maxArcWidth = Math.max(mArcWidth, mBgArcWidth); 
  12.  
  13.    //求最小值作為實際值 
  14.  
  15.    int minSize = Math.min(w - getPaddingLeft() - getPaddingRight() - 2 * (int) maxArcWidth, 
  16.  
  17.            h - getPaddingTop() - getPaddingBottom() - 2 * (int) maxArcWidth); 
  18.  
  19.    //減去圓弧的寬度,否則會造成部分圓弧繪制在外圍 
  20.  
  21.    mRadius = minSize / 2; 
  22.  
  23.    //獲取圓的相關(guān)參數(shù) 
  24.  
  25.    mCenterPoint.x = w / 2; 
  26.  
  27.    mCenterPoint.y = h / 2; 
  28.  
  29.    //繪制圓弧的邊界 
  30.  
  31.    mRectF.left = mCenterPoint.x - mRadius - maxArcWidth / 2; 
  32.  
  33.    mRectF.top = mCenterPoint.y - mRadius - maxArcWidth / 2; 
  34.  
  35.    mRectF.right = mCenterPoint.x + mRadius + maxArcWidth / 2; 
  36.  
  37.    mRectF.bottom = mCenterPoint.y + mRadius + maxArcWidth / 2; 
  38.  
  39.    //計算文字繪制時的 baseline 
  40.  
  41.    //由于文字的baseline、descent、ascent等屬性只與textSize和typeface有關(guān),所以此時可以直接計算 
  42.  
  43.    //若value、hint、unit由同一個畫筆繪制或者需要動態(tài)設(shè)置文字的大小,則需要在每次更新后再次計算 
  44.  
  45.    mValueOffset = mCenterPoint.y - (mValuePaint.descent() + mValuePaint.ascent()) / 2; 
  46.  
  47.    mHintOffset = mCenterPoint.y * 2 / 3 - (mHintPaint.descent() + mHintPaint.ascent()) / 2; 
  48.  
  49.    mUnitOffset = mCenterPoint.y * 4 / 3 - (mUnitPaint.descent() + mUnitPaint.ascent()) / 2; 
  50.  
  51.    updateArcPaint(); 
  52.  
  53.    Log.d(TAG, "onSizeChanged: 控件大小 = " + "(" + w + ", " + h + ")" 
  54.  
  55.            + "圓心坐標(biāo) = " + mCenterPoint.toString() 
  56.  
  57.            + ";圓半徑 = " + mRadius 
  58.  
  59.            + ";圓的外接矩形 = " + mRectF.toString());  

關(guān)于 Android 中文字繪制可以參考以下兩篇文章:

1. Android 自定義View學(xué)習(xí)(三)——Paint 繪制文字屬性

2. measureText() vs .getTextBounds()

以上,已經(jīng)基本完成了 View 繪制所需全部參數(shù)的計算。接下來就是繪制圓弧及文字了。

繪制圓弧需要用到 Canvas 的

  1. // oval 為 RectF 類型,即圓弧顯示區(qū)域 
  2.  
  3. // startAngle 和 sweepAngle  均為 float 類型,分別表示圓弧起始角度和圓弧度數(shù)。3點鐘方向為0度,順時針遞增 
  4.  
  5. // 如果 startAngle < 0 或者 > 360,則相當(dāng)于 startAngle % 360 
  6.  
  7. // useCenter:如果為 true 時,在繪制圓弧時將圓心包括在內(nèi),通常用來繪制扇形 
  8.  
  9. // 繪制圓弧的畫筆 
  10.  
  11. drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint);  

為了方便計算,繪制圓弧的時候使用了 Canvas 的 rotate() 方法,對坐標(biāo)系進行了旋轉(zhuǎn)

  1. private void drawArc(Canvas canvas) { 
  2.  
  3.    // 繪制背景圓弧 
  4.  
  5.    // 從進度圓弧結(jié)束的地方開始重新繪制,優(yōu)化性能 
  6.  
  7.    canvas.save(); 
  8.  
  9.    float currentAngle = mSweepAngle * mPercent; 
  10.  
  11.    canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y); 
  12.  
  13.    // +2 是因為繪制的時候出現(xiàn)了圓弧起點有尾巴的問題 
  14.  
  15.    canvas.drawArc(mRectF, currentAngle, mSweepAngle - currentAngle + 2, false, mBgArcPaint); 
  16.  
  17.    canvas.drawArc(mRectF, 2, currentAngle, false, mArcPaint); 
  18.  
  19.    canvas.restore(); 
  20.  
  21.  

恩,圓環(huán)已經(jīng)繪制完成,那么接下來就是實現(xiàn)圓環(huán)的漸變,這里使用 SweepGradient 類。SweepGradient 可以實現(xiàn)從中心放射性漸變的效果,如下圖: 

 

 

 

SweepGradient 類有兩個構(gòu)造方法,

  1. /** 
  2.  
  3. * @param cx 渲染中心點x坐標(biāo) 
  4.  
  5. * @param cy 渲染中心點y坐標(biāo) 
  6.  
  7. * @param colors 圍繞中心渲染的顏色數(shù)組,至少要有兩種顏色值 
  8.  
  9. * @param positions 相對位置的顏色數(shù)組,可為null,  若為null,可為null,顏色沿漸變線均勻分布。一般不需要設(shè)置該參數(shù) 
  10.  
  11.  
  12. public SweepGradient(float cx, float cy, int[] colors, float[] positions) 
  13.  
  14.   
  15.  
  16. /** 
  17.  
  18. * @param cx 渲染中心點x坐標(biāo) 
  19.  
  20. * @param cy 渲染中心點y坐標(biāo) 
  21.  
  22. * @param color0 起始渲染顏色 
  23.  
  24. * @param color1 結(jié)束渲染顏色 
  25.  
  26.  
  27. public SweepGradient(float cx, float cy, int color0, int color1)  

這里我們選擇***個構(gòu)造方法。由于設(shè)置漸變需要每次都創(chuàng)建一個新的 SweepGradient 對象,所以***不要放到 onDraw 方法中去更新,***在初始化的時候就設(shè)置好,避免頻繁創(chuàng)建導(dǎo)致內(nèi)存抖動。

  1. private void updateArcPaint() { 
  2.  
  3.    // 設(shè)置漸變 
  4.  
  5.    int[] mGradientColors = {Color.GREEN, Color.YELLOW, Color.RED}; 
  6.  
  7.    mSweepGradient = new SweepGradient(mCenterPoint.x, mCenterPoint.y, mGradientColors, null); 
  8.  
  9.    mArcPaint.setShader(mSweepGradient); 
  10.  
  11.  

這里還有一個值得注意的地方,草圖如下 

 

 

 

假設(shè),漸變顏色如下:

  1. int[] mGradientColors = {Color.GREEN, Color.YELLOW, Color.RED, Color.BLUE}; 

因為 SweepGradient 漸變是 360 度的,所以如果你繪制的圓弧只有 270度,則藍色部分(圖中黑色陰影部分)的漸變就會不可見。

接下來,就是文字的繪制了。文字繪制在上述提到的文章中已經(jīng)進行了詳細的講解,這里就不再贅述。代碼如下:

  1. private void drawText(Canvas canvas) { 
  2.  
  3.    canvas.drawText(String.format(mPrecisionFormat, mValue), mCenterPoint.x, mValueOffset, mValuePaint); 
  4.  
  5.   
  6.  
  7.    if (mHint != null) { 
  8.  
  9.        canvas.drawText(mHint.toString(), mCenterPoint.x, mHintOffset, mHintPaint); 
  10.  
  11.    } 
  12.  
  13.   
  14.  
  15.    if (mUnit != null) { 
  16.  
  17.        canvas.drawText(mUnit.toString(), mCenterPoint.x, mUnitOffset, mUnitPaint); 
  18.  
  19.    } 
  20.  
  21.  

***,我們來實現(xiàn)進度條的動畫效果。這里我們使用 Android 的屬性動畫來實現(xiàn)進度更新。

  1. private void startAnimator(float start, float end, long animTime) { 
  2.  
  3.    mAnimator = ValueAnimator.ofFloat(start, end); 
  4.  
  5.    mAnimator.setDuration(animTime); 
  6.  
  7.    mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 
  8.  
  9.        @Override 
  10.  
  11.        public void onAnimationUpdate(ValueAnimator animation) { 
  12.  
  13.            mPercent = (float) animation.getAnimatedValue(); 
  14.  
  15.            mValue = mPercent * mMaxValue; 
  16.  
  17.            if (BuildConfig.DEBUG) { 
  18.  
  19.                Log.d(TAG, "onAnimationUpdate: percent = " + mPercent 
  20.  
  21.                        + ";currentAngle = " + (mSweepAngle * mPercent) 
  22.  
  23.                        + ";value = " + mValue); 
  24.  
  25.            } 
  26.  
  27.            invalidate(); 
  28.  
  29.        } 
  30.  
  31.    }); 
  32.  
  33.    mAnimator.start(); 
  34.  
  35.  

這里有兩個注意點:

1. 不要在 ValueAnimator.AnimatorUpdateListener 中輸出 Log,特別是動畫調(diào)用頻繁的情況下,因為輸出 Log 頻繁會生成大量 String 對象造成內(nèi)存抖動,當(dāng)然也可以使用 StringBuilder 來優(yōu)化。

2. 關(guān)于 invalidate() 和 postInvalidate() 兩者最本質(zhì)的前者只能在 UI 線程中使用,而后者可以在非 UI 線程中使用,其實 postInvalidate() 內(nèi)部也是使用 Handler 實現(xiàn)的。

關(guān)于 Android 屬性動畫可以參考:

1. Android 屬性動畫(Property Animation) 完全解析 (上)

2. Android 屬性動畫(Property Animation) 完全解析 (下)

補充:同一個屬性如何支持顏色和顏色數(shù)組

考慮到圓弧設(shè)置單色和漸變的區(qū)別,即單色只需要提供一種色值,而漸變至少需要提供兩種色值。可以有以下幾種解決方案:

  1. 定義兩個屬性,漸變的優(yōu)先級高于單色的。
  2. 定義一個 format 為 string 屬性,以 #FFFFFF|#000000 形式提供色值
  3. 定義一個 format 為 color|reference 的屬性,其中 reference 屬性指代漸變色的數(shù)組。

這里選用第三種方案,實現(xiàn)如下:

  1. &lt;!-- 圓形進度條 --&gt; 
  2.  
  3. &lt;declare-styleable name="CircleProgressBar"&gt; 
  4.  
  5.     &lt;!-- 圓弧顏色, --&gt; 
  6.  
  7.     &lt;attr name="arcColors" format="color|reference" /&gt; 
  8.  
  9. &lt;/declare-styleable&gt; 
  10.  
  11.   
  12.  
  13. &lt;!-- colors.xml --&gt; 
  14.  
  15. &lt;color name="green"&gt;#00FF00&lt;/color&gt; 
  16.  
  17. &lt;color name="blue"&gt;#EE9A00&lt;/color&gt; 
  18.  
  19. &lt;color name="red"&gt;#EE0000&lt;/color&gt; 
  20.  
  21. &lt;!-- 漸變顏色數(shù)組 --&gt; 
  22.  
  23. &lt;integer-array name="gradient_arc_color"&gt; 
  24.  
  25.    &lt;item&gt;@color/green&lt;/item&gt; 
  26.  
  27.    &lt;item&gt;@color/blue&lt;/item&gt; 
  28.  
  29.    &lt;item&gt;@color/red&lt;/item&gt; 
  30.  
  31. &lt;/integer-array&gt; 
  32.  
  33.   
  34.  
  35. &lt;!-- 布局文件中使用 --&gt; 
  36.  
  37. &lt;!-- 使用漸變 --&gt; 
  38.  
  39. &lt;com.littlejie.circleprogress.DialProgress 
  40.  
  41.     android:id="@+id/dial_progress_bar" 
  42.  
  43.     android:layout_width="300dp" 
  44.  
  45.     android:layout_height="300dp" 
  46.  
  47.     app:arcColors="@array/gradient_arc_color" /&gt; 
  48.  
  49. &lt;!-- 使用單色 --&gt;     
  50.  
  51. &lt;com.littlejie.circleprogress.DialProgress 
  52.  
  53.     android:id="@+id/dial_progress_bar" 
  54.  
  55.     android:layout_width="300dp" 
  56.  
  57.     android:layout_height="300dp" 
  58.  
  59.     app:arcColors="@color/green" /&gt;  

代碼中讀取 xml 中配置:

  1. int gradientArcColors = typedArray.getResourceId(R.styleable.CircleProgressBar_arcColors, 0); 
  2.  
  3.    if (gradientArcColors != 0) { 
  4.  
  5.        try { 
  6.  
  7.            int[] gradientColors = getResources().getIntArray(gradientArcColors); 
  8.  
  9.            if (gradientColors.length == 0) {//如果漸變色為數(shù)組為0,則嘗試以單色讀取色值 
  10.  
  11.                int color = getResources().getColor(gradientArcColors); 
  12.  
  13.                mGradientColors = new int[2]; 
  14.  
  15.                mGradientColors[0] = color; 
  16.  
  17.                mGradientColors[1] = color; 
  18.  
  19.            } else if (gradientColors.length == 1) {//如果漸變數(shù)組只有一種顏色,默認設(shè)為兩種相同顏色 
  20.  
  21.                mGradientColors = new int[2]; 
  22.  
  23.                mGradientColors[0] = gradientColors[0]; 
  24.  
  25.                mGradientColors[1] = gradientColors[0]; 
  26.  
  27.            } else { 
  28.  
  29.                mGradientColors = gradientColors; 
  30.  
  31.            } 
  32.  
  33.        } catch (Resources.NotFoundException e) { 
  34.  
  35.            throw new Resources.NotFoundException("the give resource not found."); 
  36.  
  37.        } 
  38.  
  39.    }  

帶刻度進度條

前面,詳細講了 CircleProgress 的繪制思路,接下來講 DialProgress。

實話說,DialProgress 與 CircleProgress 的實現(xiàn)極其相似,因為兩者之間其實就差了一個刻度,但考慮到擴展以及類職責(zé)的單一,所以將兩者分開。

這里主要講一下刻度的繪制。刻度繪制主要使用 Canvas 類的 save()、rotate()和restore() 方法,當(dāng)然你也可以使用 translate() 方法對坐標(biāo)系進行平移,方便計算。

  1. /** 
  2.  
  3. * 用來保存Canvas的狀態(tài)。save之后,可以調(diào)用Canvas的平移、放縮、旋轉(zhuǎn)、錯切、裁剪等操作。 
  4.  
  5. */ 
  6.  
  7. public void save() 
  8.  
  9.   
  10.  
  11. /** 
  12.  
  13. * 旋轉(zhuǎn)一定的角度繪制圖像 
  14.  
  15. * @param degrees 旋轉(zhuǎn)角度 
  16.  
  17. * @param x 旋轉(zhuǎn)中心點x軸坐標(biāo) 
  18.  
  19. * @param y 旋轉(zhuǎn)中心點y軸坐標(biāo) 
  20.  
  21. */ 
  22.  
  23. public void rotate(float degrees, float x, float y) 
  24.  
  25.   
  26.  
  27. /** 
  28.  
  29. * 在當(dāng)前的坐標(biāo)上平移(x,y)個像素單位 
  30.  
  31. * 若dx <0 ,沿x軸向上平移; dx >0  沿x軸向下平移 
  32.  
  33. * 若dy <0 ,沿y軸向上平移; dy >0  沿y軸向下平移 
  34.  
  35. */ 
  36.  
  37. public void translate(float dx, float dy) 
  38.  
  39.   
  40.  
  41. /** 
  42.  
  43. * 用來恢復(fù)Canvas之前保存的狀態(tài)。防止save后對Canvas執(zhí)行的操作對后續(xù)的繪制有影響。 
  44.  
  45. */ 
  46.  
  47. public void restore() 
  48.  
  49.  
  50. private void drawDial(Canvas canvas) { 
  51.  
  52.    int total = (int) (mSweepAngle / mDialIntervalDegree); 
  53.  
  54.    canvas.save(); 
  55.  
  56.    canvas.rotate(mStartAngle, mCenterPoint.x, mCenterPoint.y); 
  57.  
  58.    for (int i = 0; i <= total; i++) { 
  59.  
  60.        canvas.drawLine(mCenterPoint.x + mRadius, mCenterPoint.y, mCenterPoint.x + mRadius + mArcWidth, mCenterPoint.y, mDialPaint); 
  61.  
  62.        canvas.rotate(mDialIntervalDegree, mCenterPoint.x, mCenterPoint.y); 
  63.  
  64.    } 
  65.  
  66.    canvas.restore(); 
  67.  
  68.  

關(guān)于 Canvas 的畫布操作可以參考這篇文章:安卓自定義View進階-Canvas之畫布操作

水波紋效果的進度條

水波紋效果的進度條實現(xiàn)需要用到貝塞爾曲線,主要難點在于 繪制區(qū)域的計算 和 波浪效果 的實現(xiàn),其余的邏輯跟上述兩種進度條相似。

這里使用了 Path 類,該類在 Android 2D 繪圖中是非常重要的,Path 不僅能夠繪制簡單圖形,也可以繪制這些比較復(fù)雜的圖形。也可以對多個路徑進行布爾操作,類似設(shè)置 Paint 的 setXfermode() ,具體使用可以參考這篇博客:安卓自定義View進階-Path基本操作。這里就不再贅述,有機會自己也會對 Android 自定義 View 的知識進行總結(jié),不過,感覺應(yīng)該了了無期。

繼續(xù)上示意圖,請叫我靈魂畫手~ 

 

 

 

圖中黑色的圓為我們要繪制的進度條圓,黑色的曲線為初始狀態(tài)的的波浪,該波浪使用貝塞爾曲線繪制,其中奇數(shù)的點為貝塞爾曲線的起始點,偶數(shù)的點為貝塞爾曲線的控制點。例如:1——>2——>3就為一條貝塞爾曲線,1 是起點,2 是控制點,3 是終點。從圖中可以看到波浪在園內(nèi)圓外各一個(1—>5 和 5->9),通過對波浪在 x 軸上做平移,即圖中藍色實線,來實現(xiàn)波浪的動態(tài)效果,所以一個波浪的完整動畫效果需要有兩個波浪來實現(xiàn)。同理,通過控制 y 軸的偏移量,即圖中藍色虛線,可以實現(xiàn)波浪隨進度的上漲下降。

貝塞爾曲線上起始點和控制點的計算如下:

  1. /** 
  2.  
  3. * 計算貝塞爾曲線上的起始點和控制點 
  4.  
  5. * @param waveWidth 一個完整波浪的寬度 
  6.  
  7. */ 
  8.  
  9. private Point[] getPoint(float waveWidth) { 
  10.  
  11.    Point[] points = new Point[mAllPointCount]; 
  12.  
  13.    //第1個點特殊處理,即數(shù)組的中心 
  14.  
  15.    points[mHalfPointCount] = new Point((int) (mCenterPoint.x - mRadius), mCenterPoint.y); 
  16.  
  17.    //屏幕內(nèi)的貝塞爾曲線點 
  18.  
  19.    for (int i = mHalfPointCount + 1; i < mAllPointCount; i += 4) { 
  20.  
  21.        float width = points[mHalfPointCount].x + waveWidth * (i / 4 - mWaveNum); 
  22.  
  23.        points[i] = new Point((int) (waveWidth / 4 + width), (int) (mCenterPoint.y - mWaveHeight)); 
  24.  
  25.        points[i + 1] = new Point((int) (waveWidth / 2 + width), mCenterPoint.y); 
  26.  
  27.        points[i + 2] = new Point((int) (waveWidth * 3 / 4 + width), (int) (mCenterPoint.y + mWaveHeight)); 
  28.  
  29.        points[i + 3] = new Point((int) (waveWidth + width), mCenterPoint.y); 
  30.  
  31.    } 
  32.  
  33.    //屏幕外的貝塞爾曲線點 
  34.  
  35.    for (int i = 0; i < mHalfPointCount; i++) { 
  36.  
  37.        int reverse = mAllPointCount - i - 1; 
  38.  
  39.        points[i] = new Point(points[mHalfPointCount].x - points[reverse].x, 
  40.  
  41.                points[mHalfPointCount].y * 2 - points[reverse].y); 
  42.  
  43.    } 
  44.  
  45.    return points; 
  46.  
  47.  

以上,我們已經(jīng)獲取到繪制貝塞爾曲線所需的路徑點。接下來,我們就需要來計算出繪制區(qū)域,即使用 Path 類。 

 

 

 

紫色區(qū)域為貝塞爾曲線需要繪制的整體區(qū)域。 

 

 

 

紅色區(qū)域為上圖紫色區(qū)域與圓的交集,也就是波浪要顯示的區(qū)域

代碼如下:

  1. //該方法必須在 Android 19以上的版本才能使用(Path.op()) 
  2.  
  3. @TargetApi(Build.VERSION_CODES.KITKAT) 
  4.  
  5. private void drawWave(Canvas canvas, Paint paint, Point[] points, float waveOffset) { 
  6.  
  7.    mWaveLimitPath.reset(); 
  8.  
  9.    mWavePath.reset(); 
  10.  
  11.    //lockWave 用于判斷波浪是否隨進度條上漲下降 
  12.  
  13.    float height = lockWave ? 0 : mRadius - 2 * mRadius * mPercent; 
  14.  
  15.    //moveTo和lineTo繪制出水波區(qū)域矩形 
  16.  
  17.    mWavePath.moveTo(points[0].x + waveOffset, points[0].y + height); 
  18.  
  19.   
  20.  
  21.    for (int i = 1; i < mAllPointCount; i += 2) { 
  22.  
  23.        mWavePath.quadTo(points[i].x + waveOffset, points[i].y + height, 
  24.  
  25.                points[i + 1].x + waveOffset, points[i + 1].y + height); 
  26.  
  27.    } 
  28.  
  29.    mWavePath.lineTo(points[mAllPointCount - 1].x, points[mAllPointCount - 1].y + height); 
  30.  
  31.    //不管如何移動,波浪與圓路徑的交集底部永遠固定,否則會造成上移的時候底部為空的情況 
  32.  
  33.    mWavePath.lineTo(points[mAllPointCount - 1].x, mCenterPoint.y + mRadius); 
  34.  
  35.    mWavePath.lineTo(points[0].x, mCenterPoint.y + mRadius); 
  36.  
  37.    mWavePath.close(); 
  38.  
  39.    mWaveLimitPath.addCircle(mCenterPoint.x, mCenterPoint.y, mRadius, Path.Direction.CW); 
  40.  
  41.    //取該圓與波浪路徑的交集,形成波浪在圓內(nèi)的效果 
  42.  
  43.    mWaveLimitPath.op(mWavePath, Path.Op.INTERSECT); 
  44.  
  45.    canvas.drawPath(mWaveLimitPath, paint);  

以上,就實現(xiàn)了水波動態(tài)的效果,當(dāng)然,你也可以通過配置,來設(shè)定水波是否隨進度上漲下降。為了實現(xiàn)更好的效果,可以設(shè)置一個淺色的水波并支持設(shè)置水波的走向(R2L 和 L2R),通過設(shè)置淺色波浪和深色波浪動畫的時間,從而實現(xiàn)長江后浪推前浪的效果,恩,效果很自然的~自己腦補從右至左波浪的實現(xiàn)和貝塞爾點的計算。

對獲取坐標(biāo)點的代碼進行優(yōu)化:

  1. /** 
  2.  
  3. * 從左往右或者從右往左獲取貝塞爾點 
  4.  
  5.  
  6. * @return 
  7.  
  8. */ 
  9.  
  10. private Point[] getPoint(boolean isR2L, float waveWidth) { 
  11.  
  12.    Point[] points = new Point[mAllPointCount]; 
  13.  
  14.    //第1個點特殊處理,即數(shù)組的中點 
  15.  
  16.    points[mHalfPointCount] = new Point((int) (mCenterPoint.x + (isR2L ? mRadius : -mRadius)), mCenterPoint.y); 
  17.  
  18.    //屏幕內(nèi)的貝塞爾曲線點 
  19.  
  20.    for (int i = mHalfPointCount + 1; i < mAllPointCount; i += 4) { 
  21.  
  22.        float width = points[mHalfPointCount].x + waveWidth * (i / 4 - mWaveNum); 
  23.  
  24.        points[i] = new Point((int) (waveWidth / 4 + width), (int) (mCenterPoint.y - mWaveHeight)); 
  25.  
  26.        points[i + 1] = new Point((int) (waveWidth / 2 + width), mCenterPoint.y); 
  27.  
  28.        points[i + 2] = new Point((int) (waveWidth * 3 / 4 + width), (int) (mCenterPoint.y + mWaveHeight)); 
  29.  
  30.        points[i + 3] = new Point((int) (waveWidth + width), mCenterPoint.y); 
  31.  
  32.    } 
  33.  
  34.    //屏幕外的貝塞爾曲線點 
  35.  
  36.    for (int i = 0; i < mHalfPointCount; i++) { 
  37.  
  38.        int reverse = mAllPointCount - i - 1; 
  39.  
  40.        points[i] = new Point((isR2L ? 2 : 1) * points[mHalfPointCount].x - points[reverse].x, 
  41.  
  42.                points[mHalfPointCount].y * 2 - points[reverse].y); 
  43.  
  44.    } 
  45.  
  46.    //對從右向左的貝塞爾點數(shù)組反序,方便后續(xù)處理 
  47.  
  48.    return isR2L ? MiscUtil.reverse(points) : points; 
  49.  
  50.  

至此,自定義圓形進度條相關(guān)的思路已全部講述完成。代碼已全部上傳至 Git ,歡迎大家 Star 和 Fork,傳送門:CircleProgress。

https://github.com/MyLifeMyTravel/CircleProgress 

責(zé)任編輯:龐桂玉 來源: 安卓開發(fā)精選
相關(guān)推薦

2021-12-30 16:10:52

鴻蒙HarmonyOS應(yīng)用

2021-09-06 14:58:23

鴻蒙HarmonyOS應(yīng)用

2022-09-09 14:47:50

CircleArkUI

2021-12-07 18:23:50

自定義進度條分段式

2016-12-26 15:25:59

Android自定義View

2016-11-16 21:55:55

源碼分析自定義view androi

2013-05-20 17:33:44

Android游戲開發(fā)自定義View

2016-04-12 10:07:55

AndroidViewList

2021-10-26 10:07:02

鴻蒙HarmonyOS應(yīng)用

2011-08-02 11:17:13

iOS開發(fā) View

2023-07-18 15:49:22

HTMLCSS

2015-07-31 11:19:43

數(shù)字進度條源碼

2015-01-12 12:13:03

Android進度條ProgressDia

2010-01-25 18:27:54

Android進度條

2015-01-12 09:30:54

Android進度條ProgressDia

2017-03-02 13:33:19

Android自定義View

2011-07-05 15:16:00

QT 進度條

2024-08-06 14:29:37

2011-02-22 14:53:41

titlebar標(biāo)題欄Android

2012-05-18 10:52:20

TitaniumAndroid模塊自定義View模塊
點贊
收藏

51CTO技術(shù)棧公眾號

蜜桃视频在线观看播放| 中文在线最新版天堂| 综合伊人久久| 欧美日韩国产综合新一区 | 99久久99九九99九九九| 一区二区三区久久久| 蜜桃传媒一区二区| 97精品人妻一区二区三区在线 | 欧美日韩大尺度| 麻豆网站在线观看| 成人午夜电影网站| 国产主播欧美精品| 欧美一级视频免费观看| 国产精品久久久乱弄| 亚洲精品久久久久久下一站| 超碰超碰在线观看| 免费成人在线电影| 亚洲精品中文在线| 日韩欧美手机在线| 四虎永久在线观看| 精品中文字幕一区二区| 欧美在线视频网站| 麻豆changesxxx国产| 欧美精品一区二区久久| 亚洲电影av在线| 一级做a爱视频| 日韩精选视频| 欧美日韩免费观看中文| 日本a级片在线播放| 91caoporm在线视频| 91视频免费观看| 97久久人人超碰caoprom欧美| 欧美成人一区二区视频| 午夜在线视频一区二区区别| 久久99国产精品自在自在app | 欧美伊人影院| 色婷婷综合久久久久中文字幕1| 久久人人爽人人爽人人片| 亚洲国产一区二区三区网站| 欧美区在线观看| 国产精品视频分类| 性欧美freehd18| 色婷婷av一区二区三区软件| 无码人妻丰满熟妇区96| caoporn-草棚在线视频最| 中文字幕精品一区| 亚洲欧美日本国产有色| 成人高清免费观看mv| 久久久电影一区二区三区| 狠狠色综合网站久久久久久久| 性欧美18一19性猛交| 国产一区不卡视频| 亚洲japanese制服美女| 国产又粗又猛又黄| 经典三级在线一区| 91热精品视频| 国产伦精品一区二区三区免.费 | 亚洲精品国产成人av在线| 亚洲无线观看| 亚洲第一网站免费视频| 亚洲香蕉中文网| 欧美精品密入口播放| 日韩黄色av网站| 波多野结衣 在线| 精品一区二区三区在线| 三级精品视频久久久久| 在线看的片片片免费| 欧美精品二区| 2019最新中文字幕| 成人毛片在线播放| 欧美bbbbb| 国产在线高清精品| 亚洲第一页综合| www.久久精品| 欧美日韩亚洲综合一区二区三区激情在线| 青青草在线免费观看| 久久蜜臀精品av| 亚洲国产精品一区二区第四页av| 麻豆传媒在线完整视频| 亚洲精品第一国产综合野| 丰满少妇久久久| 日韩不卡视频在线观看| 91精品婷婷国产综合久久性色| 在线观看你懂的视频| 日本国产精品| 丝袜亚洲欧美日韩综合| 久久久国产精品人人片| 久久一区精品| 亚洲一区中文字幕| 头脑特工队2在线播放| 欧美激情综合在线| 国产一区 在线播放| 亚洲妇女成熟| 91精品国产综合久久精品app| 稀缺呦国内精品呦| 凹凸成人精品亚洲精品密奴| 欧美日韩福利视频| 日本黄色一级视频| 国产成人欧美日韩在线电影| 欧美二区三区| av免费看在线| 在线免费观看日本欧美| 亚洲911精品成人18网站| 小说区图片区色综合区| 久久成年人免费电影| 日本视频在线观看免费| 国产一区二区三区久久悠悠色av| 美女黄毛**国产精品啪啪| a级网站在线播放| 色老汉av一区二区三区| 天堂va欧美va亚洲va老司机| 成人羞羞动漫| 91地址最新发布| 国产草草影院ccyycom| 国产欧美一区在线| 男人添女人下部高潮视频在观看 | 不卡一卡2卡3卡4卡精品在| 你懂的免费在线观看视频网站| 亚洲色图在线播放| 日本xxxxxxx免费视频| 国产精品nxnn| 久久天天躁狠狠躁夜夜躁| 亚洲精品久久久久久久蜜桃| 不卡的av在线| 男人日女人的bb| 日本国产亚洲| 中文字幕日韩高清| 五月婷婷激情视频| 97精品国产露脸对白| 777久久精品一区二区三区无码| 久久亚洲人体| 国产午夜精品一区理论片飘花| 国产视频91在线| 成人在线综合网| 青青青在线观看视频| www.欧美| 美女精品久久久| 国产精品玖玖玖| 国产精品网站导航| 一道本视频在线观看| 精品72久久久久中文字幕| 欧美野外猛男的大粗鳮| 特级丰满少妇一级aaaa爱毛片| 亚洲图片欧美一区| 人妖粗暴刺激videos呻吟| 韩国av一区| 国产精品国产精品| f2c人成在线观看免费视频| 欧美精品一区二区三区高清aⅴ | 国产一区二区三区四区五区在线 | 蜜桃色一区二区三区| 女人天堂亚洲aⅴ在线观看| 91精品在线观看视频| 成人直播在线| 日韩精品在线看片z| 久一区二区三区| 99视频国产精品| 91精品91久久久中77777老牛| 日韩有码一区| 国产福利视频一区二区| 超碰在线影院| 日韩欧美在线1卡| 国产小视频在线观看免费| av亚洲精华国产精华精华| 国产91美女视频| 精品一区av| 成人午夜在线观看| 免费影视亚洲| 精品亚洲一区二区| 中文字幕 视频一区| 亚洲日本va午夜在线影院| 99riav国产精品视频| 国产女优一区| 先锋影音亚洲资源| 亚洲精品一区国产| 日韩美女视频中文字幕| 在线免费av电影| 日韩亚洲国产中文字幕欧美| 精品美女久久久久| 国产剧情在线观看一区| 国产精品欧美亚洲777777| 久久77777| 亚洲电影免费观看高清完整版在线观看 | 久久精品小视频| 亚洲女同志亚洲女同女播放| 欧美午夜精品久久久久久久| 快灬快灬一下爽蜜桃在线观看| 极品美女销魂一区二区三区| 18禁裸男晨勃露j毛免费观看| 亚洲三级性片| 亚洲tv在线观看| 中文字幕在线直播| 久久久精品一区二区| 婷婷伊人综合中文字幕| 欧美精品在线一区二区三区| 日韩乱码一区二区| 成人免费一区二区三区视频| 亚洲少妇18p| 久久草av在线| 国产中文字幕视频在线观看| 日韩啪啪电影网| 国产日韩欧美一区二区| 国产91亚洲精品久久久| 久久人人爽人人爽人人片av高请| h视频在线播放| 精品久久国产老人久久综合| 亚洲影视一区二区| 欧美网站在线观看| 九九视频免费观看| 国产精品久久久久久久久图文区 | 国产精品国产自产拍高清av| 你懂的在线观看网站| 激情欧美日韩一区二区| 999精品网站| 亚洲区欧美区| 日本美女爱爱视频| 色爱综合网欧美| 欧美在线3区| 国产亚洲成av人片在线观黄桃| 国产精品中文字幕在线| 成人福利av| 国内精品久久久久伊人av| 里番在线观看网站| 一本色道久久综合亚洲精品小说 | 久久久国产精品免费| 国产视频第一页在线观看| 亚洲国产高潮在线观看| 99在线小视频| 欧美日韩高清一区二区| 五月婷婷激情视频| 色综合一个色综合| 日韩福利片在线观看| 亚洲午夜精品在线| 国产精品久久久久久久精| 亚洲视频一区在线| 疯狂撞击丝袜人妻| 国产精品电影院| 毛片久久久久久| 国产精品欧美一区喷水| 国产又粗又猛又爽又黄av| 久久久亚洲精品一区二区三区| 99re久久精品国产| 99精品视频在线观看免费| 精品视频站长推荐| aaa国产一区| 老司机午夜免费福利| aaa亚洲精品| 可以直接看的无码av| 久久久久久综合| 免费观看a级片| 国产精品国产三级国产三级人妇| 亚洲不卡的av| 亚洲视频资源在线| 精品99久久久久成人网站免费 | 久久久亚洲精品无码| 99精品国产在热久久下载| 欧美日韩一道本| 久久人人超碰| 天天色综合社区| 激情另类小说区图片区视频区| 成人免费播放视频| 成人久久18免费网站麻豆| 免费看黄色aaaaaa 片| 国产欧美日韩另类一区| 91制片厂在线| 一个色在线综合| 日韩av免费网址| 日本国产一区二区| 一本大道伊人av久久综合| 91精品在线免费| 黄色av中文字幕| 亚洲女成人图区| 香蕉视频在线看| 欧美裸身视频免费观看| 国产在线88av| 国产精品无av码在线观看| 精品精品视频| 精品一区二区三区视频日产| 欧美日韩国产一区二区三区不卡| 一区二区三区国| 亚洲精品社区| 天天干天天草天天| 成人激情视频网站| www.99热| 亚洲一区影音先锋| 毛片在线免费播放| 日韩午夜精品电影| 黄色美女网站在线观看| 久久国产精品久久久久久久久久| 草草在线观看| 91精品久久久久久| 色狼人综合干| 精品91一区二区三区| 国产一区二区三区的电影| 青青草原国产在线视频| 99国产精品国产精品毛片| 美国美女黄色片| 午夜视频在线观看一区二区三区| 久久久999久久久| 亚洲国产另类 国产精品国产免费| gogogo高清在线观看免费完整版| 久久久久久亚洲精品不卡| 国产精品麻豆成人av电影艾秋| 国产精品免费视频一区二区| 四虎成人av| 青青艹视频在线| 国产成人综合网| 亚洲精品自拍视频在线观看| 婷婷成人综合网| 欧美性x x x| 欧美日韩性视频| 精品久久久久成人码免费动漫| 亚洲午夜未删减在线观看| 91美女主播在线视频| 亚洲aaa激情| 色欧美自拍视频| 久久精品午夜福利| www.亚洲在线| 欧美黄色免费看| 欧美二区三区的天堂| 国产亚洲依依| 日本91av在线播放| 另类ts人妖一区二区三区| 国产精品8888| 精品无码三级在线观看视频| 毛片aaaaaa| 91久久人澡人人添人人爽欧美| 蜜桃视频久久一区免费观看入口| 久久亚洲精品毛片| 欧美天堂一区二区| 亚洲国产激情一区二区三区| 另类激情亚洲| 熟女人妻在线视频| 亚洲成人动漫一区| 亚洲免费一级片| 欧美大片免费看| 日韩欧美一级| 51xx午夜影福利| 国产一区啦啦啦在线观看| 三级黄色片在线观看| 欧美色老头old∨ideo| 成人午夜在线观看视频| 国产精品久久久久7777婷婷| 国产99亚洲| 成年人网站大全| 国产免费久久精品| 18国产免费视频| www.午夜精品| 国产精品3区| 国产香蕉一区二区三区| 国产精品99久久久| 日本天堂中文字幕| 精品久久久久久最新网址| 黄网站在线观| 精品视频一区二区| 久久午夜精品一区二区| 精品成人无码一区二区三区| 欧美视频在线播放| 看黄网站在线观看| 91九色视频在线观看| 激情欧美一区二区三区| 日批在线观看视频| 91成人看片片| 欧美人xxx| 成人区精品一区二区| 亚洲毛片在线| 熟女少妇内射日韩亚洲| 欧美日韩日本视频| 欧美videossex| 久久久久欧美| 麻豆精品视频在线| 中文字幕av久久爽av| 亚洲电影免费观看高清完整版在线| 中文字幕资源网在线观看免费| 亚洲欧洲日韩综合二区| 国产精品456| 久久国产精品免费看| 在线观看日韩www视频免费| 电影一区二区三区久久免费观看| 国产精品免费看久久久无码| 91色乱码一区二区三区| 在线观看黄色网| 欧美激情精品久久久久久久变态| 亚洲警察之高压线| 不用播放器的免费av| 亚洲18女电影在线观看| 成人午夜影视| 国产精品播放| 美女视频一区在线观看| www.youjizz.com亚洲| 在线a欧美视频| 99ri日韩精品视频| 中文字幕国内自拍| 亚洲图片一区二区| 日韩精品成人av| 久久av免费一区| 国产精品综合一区二区| 中文字幕xxxx| 国内精品久久影院| 香蕉国产精品|