首先貼上源碼地址:https://github.com/CB-ysx/CBRatingBar
最近需要做一個(gè)星星的評(píng)分控件(可以調(diào)整進(jìn)度,進(jìn)度顏色漸變)
如圖:


一開始想到的是用系統(tǒng)自帶的RatingBar做,但發(fā)現(xiàn)了一個(gè)問(wèn)題,實(shí)現(xiàn)顏色漸變有點(diǎn)復(fù)雜,而且有好幾個(gè)頁(yè)面都用了這個(gè),總不能都這樣寫吧。剛好這段時(shí)間看了HenCoder寫的Android自定義view系列文章,于是就想自己嘗試下實(shí)現(xiàn)一個(gè)評(píng)分控件,可以實(shí)現(xiàn)圖案的替換,漸變顏色,進(jìn)度背景,圖案?jìng)€(gè)數(shù),大小等參數(shù)的自己控制,經(jīng)過(guò)幾天的折騰,終于完成了這個(gè)控件CBRatingBar
先上效果圖:




gif效果圖:

如何使用:
Gradle
- 在項(xiàng)目的build.gradle中添加如下代碼:
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
- 在項(xiàng)目的build.gradle中引入該庫(kù):
dependencies {
compile 'com.github.CB-ysx:CBRatingBar:2.0.1'
}
使用方法
- xml
<com.cb.ratingbar.CBRatingBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<com.cb.ratingbar.CBRatingBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:starSize="20dp"
app:starCount="5"
app:starSpace="10dp"
app:starStrokeWidth="1dp"
app:starCanTouch="true"
app:starMaxProgress="120"
app:starProgress="60"
app:starShowStroke="true"
app:starUseGradient="true"
app:starStartColor="#0000ff"
app:starEndColor="#00ff00"
app:starCoverColor="#ff0000"
app:starFillColor="#666666"
app:starPointCount="5"
app:starStrokeColor="#0f0f0f"
app:pathData="@string/bird"
app:pathDataId="@string/bird"/>
- java
cbRatingBar.setStarSize(20) //大小
.setStarCount(5) //數(shù)量
.setStarSpace(10) //間距
.setStarPointCount(5) //角數(shù)(n角星)
.setShowStroke(true) //是否顯示邊框
.setStarStrokeColor(Color.parseColor("#00ff00")) //邊框顏色
.setStarStrokeWidth(5) //邊框大小
.setStarFillColor(Color.parseColor("#00ff00")) //填充的背景顏色
.setStarCoverColor(Color.parseColor("#00ff00")) //填充的進(jìn)度顏色
.setStarMaxProgress(120) //最大進(jìn)度
.setStarProgress(50) //當(dāng)前顯示的進(jìn)度
.setUseGradient(true) //是否使用漸變填充(如果使用則coverColor無(wú)效)
.setStartColor(Color.parseColor("#000000")) //漸變的起點(diǎn)顏色
.setEndColor(Color.parseColor("#ffffff")) //漸變的終點(diǎn)顏色
.setCanTouch(true) //是否可以點(diǎn)擊
.setPathData(getResources().getString(R.string.pig))//傳入path的數(shù)據(jù)
.setPathDataId(R.string.pig)//傳入path數(shù)據(jù)id
.setDefaultPath()//設(shè)置使用默認(rèn)path
.setPath(path)//傳入path
.setOnStarTouchListener(new CBRatingBar.OnStarTouchListener() { //點(diǎn)擊監(jiān)聽
@Override
public void onStarTouch(int touchCount) {
Toast.makeText(MainActivity.this, "點(diǎn)擊第" + touchCount + "個(gè)星星", Toast.LENGTH_SHORT).show();
}
});
說(shuō)明
pathData為svg文件中的path數(shù)據(jù),如下:
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns:xlink="http://www.w3.org/1999/xlink" style="" class="icon" height="16" p-id="2384" t="1506306007922"
version="1.1" viewBox="0 0 1137 1024" width="17.765625" xmlns="http://www.w3.org/2000/svg">
<defs>
<style type="text/css"></style>
</defs>
<path
d="M800.653373 60.04085c-92.533023 0-174.90483 42.957261-228.601405 109.913074-53.696576-66.955813-136.068383-109.913074-228.601406-109.913074-161.838292 0-293.037297 131.199004-293.037297 293.037297 0 34.485875 6.284982 67.452386 17.216998 98.202847 82.149461 249.939217 470.047002 495.868793 504.429116 517.265896 34.374702-21.397103 422.272244-267.326679 504.421705-517.265896 10.932015-30.750461 17.216997-63.716972 17.216997-98.202847-0.007412-161.838292-131.206416-293.037297-293.044708-293.037297z"
fill="#E24B44" p-id="2385">
</path>
</svg>
以上path中"M800.653373 ... -293.037297z"這部分?jǐn)?shù)據(jù)就是要提交給控件的pathData。
如何實(shí)現(xiàn)
為了有更好的擴(kuò)展性,這我用了path數(shù)據(jù)來(lái)繪制圖案,而非單獨(dú)實(shí)現(xiàn)繪制星星,當(dāng)然,一開始確實(shí)只是實(shí)現(xiàn)了繪制星星,而且是沒有圓角效果的星星(設(shè)計(jì)圖有圓角,先湊合著用吧,哈哈)。于是在網(wǎng)上找到了星星的繪制方法:
/**
* 獲取星星的path
*
* @return
*/
private Path getStarPath(int dx) {
Path path = new Path();
float radius;
if (starPointCount % 2 == 0) {
radius = starSize * (cos(360.0 / starPointCount / 2) / 2 - sin(360.0 / starPointCount / 2) * sin(90 -
360.0 / starPointCount) / cos(90 - 360.0 / starPointCount));
} else {
radius = starSize * sin(360.0 / starPointCount / 4) / 2 / sin(180 - 360.0 / starPointCount / 2 - 360.0 /
starPointCount / 4);
}
for (int i = 0; i < starPointCount; ++i) {
if (i == 0) {
path.moveTo(starSize * cos(360 / starPointCount * i) / 2, starSize * sin(360 / starPointCount * i) /
2 + dx);
} else {
path.lineTo(starSize * cos(360.0 / starPointCount * i) / 2, starSize * sin(360.0 / starPointCount *
i) / 2 + dx);
}
path.lineTo(radius * cos(360.0 / starPointCount * i + 360.0 / starPointCount / 2), radius * sin(360.0 /
starPointCount * i + 360.0 / starPointCount / 2) + dx);
}
path.close();
return path;
}
其中dx可以先不用管,這是后面需要繪制多個(gè)星星用到的,這段代碼就可以得到星星的path,至于為什么是這樣計(jì)算,這我就沒有去探究了(趕項(xiàng)目要緊),這段代碼繪制出來(lái)的圖案如下:

沒錯(cuò),只能繪制一個(gè)星星,那要如何繪制多個(gè)星星呢,這里就用到了dx了,dx是星星的偏移量(星星寬度+兩個(gè)星星之間的間距),通過(guò)這個(gè)偏移量獲取不同位置的星星path,再繪制到畫布上就能實(shí)現(xiàn)多個(gè)星星了,代碼如下:
canvas.translate(dx, dy);
canvas.rotate(-90);
int x = 0;
for (int i = 0; i < starCount; ++i) {
Path path = getStarPath(x);
canvas.drawPath(patpaint);
x += (starSize + starSpace);
}
canvas.rotate(90);
canvas.translate(-dx, -dy);
starCount就是星星的個(gè)數(shù)
starSize就是星星的大小
starSpace就是兩個(gè)星星之間的間距
此時(shí)繪制出來(lái)的星星是填充了顏色的星星,但是還沒有進(jìn)度條,更沒有漸變的進(jìn)度條效果。
繪制效果如圖:

接下來(lái)就是實(shí)現(xiàn)進(jìn)度條,先實(shí)現(xiàn)純色的進(jìn)度條,本來(lái)繪制進(jìn)度很簡(jiǎn)單的,但是由于星星屬于不太規(guī)則的圖案(雖然已經(jīng)很規(guī)則了,但相對(duì)于矩形、圓形這些來(lái)說(shuō)還是不規(guī)則的,哈哈,不要吐槽我),加上考慮到擴(kuò)展性(可能使用的是其他真正不規(guī)則的圖案),這里用了另一種方法來(lái)填充不規(guī)則圖形。
代碼如下:
//將星星繪制到star上
Bitmap star = Bitmap.createBitmap(width, starSizeBitmap.Config.ARGB_8888);
Canvas starCanvas = neCanvas(star);
drawStar(starCanvas, starFillPaint);
//在star上填充顏色
Bitmap finalStar = Bitmap.createBitmap(width, starSizeBitmap.Config.ARGB_8888);
Canvas canvas = neCanvas(finalStar);
canvas.save();
canvas.clipRect(0, 0, dx, starSize);
canvas.drawRect(0, 0, widthstarSize, starCoverPaint);
canvas.restore();
canvas.drawBitmap(star, 0, 0, starPaint);
先創(chuàng)建一個(gè)寬為width(由星星個(gè)數(shù)及間距計(jì)算得出)高為starSize(單個(gè)星星的大小)的Bitmap,然后把星星以未選中時(shí)的背景色繪制到Bitmap上,這時(shí)得到的就是沒有任何進(jìn)度的圖。接下來(lái)是繪制進(jìn)度,我們需要再創(chuàng)建一個(gè)Bitmap,然后使用clipRect方法裁剪高度為starSize,寬度為dx(進(jìn)度)的矩形局域,然后填充進(jìn)度的顏色(可以是純色也可以是漸變色),最后把繪制了星星的bitmap繪制到這個(gè)畫布上,采用PorterDuffXfermode的畫筆,即可得到填充了進(jìn)度的星星效果,如圖:

設(shè)置畫筆:
starPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
關(guān)于畫筆的設(shè)置,可參考HenCoder的這篇文章HenCoder Android 開發(fā)進(jìn)階: 自定義 View 1-2 Paint 詳解,不得不說(shuō),這幾篇文章都寫得很不錯(cuò),對(duì)我來(lái)說(shuō),收獲真的很多。
這樣就完成了CBRatingBar的第一個(gè)版本。用在app上,又方便效果也還不錯(cuò),不足的是只有一個(gè)圖案,沒辦法自定義,于是就有了第二個(gè)版本,開發(fā)2.0.0版本雖然只是加多了自定義path數(shù)據(jù),但是這一過(guò)程也是挺坎坷的。
研究png圖片提取path數(shù)據(jù)---無(wú)果;
研究svg圖片中的path數(shù)據(jù)---有點(diǎn)希望;
于是開始學(xué)習(xí)svg語(yǔ)法,自己實(shí)現(xiàn)將svg中的pathData轉(zhuǎn)為Android繪圖中的path數(shù)據(jù),到了畫弧線這些命令就卡住了,還有網(wǎng)上看了一些svg數(shù)據(jù),發(fā)現(xiàn)其實(shí)挺坑的,有的用‘,’分割,有的用空格,感覺我的算法并不能很好地識(shí)別,又糾結(jié)了一段時(shí)間。最后在github發(fā)現(xiàn)了RichPath這個(gè)庫(kù),發(fā)現(xiàn)了該庫(kù)中有實(shí)現(xiàn)從svg提取path的算法,于是就拿過(guò)來(lái)用了,在此感謝tarek360提供的算法。
有了這個(gè)剩下的就簡(jiǎn)單多了,加多幾個(gè)方法,傳入pathData數(shù)據(jù),將之前獲取星星的path方法,改為可以從pathData中提取,這樣就實(shí)現(xiàn)了可以自定義圖案,具體代碼如下:
/**
* 初始化path
*
* @return
*/
private void initPath() {
if (pathData != null && !"".equals(pathData.trim().replace(" ", ""))) {
mPath = PathParserCompat.createPathFromPathData(pathData);
isSelfPath = true;
} else if (pathDataId != -1) {
mPath = PathParserCompat.createPathFromPathData(getResources().getString(pathDataId));
isSelfPath = true;
} else {
isSelfPath = false;
}
if (isSelfPath) {
resizePath(mPath, starSize, starSize);
}
}
其中的pathData就是原始數(shù)據(jù),PathParserCompat就是用來(lái)將數(shù)據(jù)轉(zhuǎn)為path的。由于提取出來(lái)的path是svg本來(lái)的大小,所以需要將它縮減為我們?cè)O(shè)置的大小,也就是用resizePath這個(gè)方法:
public void resizePath(Path path, float width, float height) {
RectF bounds = new RectF(0, 0, width, height);
RectF src = new RectF();
path.computeBounds(src, true);
Matrix resizeMatrix = new Matrix();
resizeMatrix.setRectToRect(src, bounds, Matrix.ScaleToFit.FILL);
path.transform(resizeMatrix);
}
這樣我們就可以很方便地使用我們自己的圖案來(lái)繪制了。
最后再貼上源碼地址:https://github.com/CB-ysx/CBRatingBar
歡迎star~
關(guān)于svg語(yǔ)法,可查看W3C School