首先貼上源碼地址:https://github.com/CB-ysx/CBRatingBar
最近需要做一個(gè)星星的評(píng)分控件(可以調(diào)整進(jìn)度,進(jìn)度顏色漸變)
如圖:
一開(kāi)始想到的是用系統(tǒng)自帶的RatingBar做,但發(fā)現(xiàn)了一個(gè)問(wèn)題谜诫,實(shí)現(xiàn)顏色漸變有點(diǎn)復(fù)雜,而且有好幾個(gè)頁(yè)面都用了這個(gè),總不能都這樣寫(xiě)吧淀零。剛好這段時(shí)間看了HenCoder寫(xiě)的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)聽(tīng)
@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)然科乎,一開(kāi)始確實(shí)只是實(shí)現(xiàn)了繪制星星壁畸,而且是沒(méi)有圓角效果的星星(設(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ì)算腻异,這我就沒(méi)有去探究了(趕項(xiàng)目要緊),這段代碼繪制出來(lái)的圖案如下:
沒(méi)錯(cuò)进副,只能繪制一個(gè)星星,那要如何繪制多個(gè)星星呢悔常,這里就用到了dx了影斑,dx是星星的偏移量(星星寬度+兩個(gè)星星之間的間距),通過(guò)這個(gè)偏移量獲取不同位置的星星path机打,再繪制到畫(huà)布上就能實(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)的星星是填充了顏色的星星,但是還沒(méi)有進(jìn)度條残邀,更沒(méi)有漸變的進(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í)得到的就是沒(méi)有任何進(jìn)度的圖循榆。接下來(lái)是繪制進(jìn)度析恢,我們需要再創(chuàng)建一個(gè)Bitmap,然后使用clipRect方法裁剪高度為starSize冯痢,寬度為dx(進(jìn)度)的矩形局域氮昧,然后填充進(jìn)度的顏色(可以是純色也可以是漸變色),最后把繪制了星星的bitmap繪制到這個(gè)畫(huà)布上浦楣,采用PorterDuffXfermode的畫(huà)筆袖肥,即可得到填充了進(jìn)度的星星效果,如圖:
設(shè)置畫(huà)筆:
starPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
關(guān)于畫(huà)筆的設(shè)置振劳,可參考HenCoder的這篇文章HenCoder Android 開(kāi)發(fā)進(jìn)階: 自定義 View 1-2 Paint 詳解椎组,不得不說(shuō),這幾篇文章都寫(xiě)得很不錯(cuò)历恐,對(duì)我來(lái)說(shuō)寸癌,收獲真的很多。
這樣就完成了CBRatingBar的第一個(gè)版本弱贼。用在app上蒸苇,又方便效果也還不錯(cuò),不足的是只有一個(gè)圖案吮旅,沒(méi)辦法自定義溪烤,于是就有了第二個(gè)版本,開(kāi)發(fā)2.0.0版本雖然只是加多了自定義path數(shù)據(jù)庇勃,但是這一過(guò)程也是挺坎坷的檬嘀。
研究png圖片提取path數(shù)據(jù)---無(wú)果;
研究svg圖片中的path數(shù)據(jù)---有點(diǎn)希望责嚷;
于是開(kāi)始學(xué)習(xí)svg語(yǔ)法鸳兽,自己實(shí)現(xiàn)將svg中的pathData轉(zhuǎn)為Android繪圖中的path數(shù)據(jù),到了畫(huà)弧線(xiàn)這些命令就卡住了罕拂,還有網(wǎng)上看了一些svg數(shù)據(jù)揍异,發(fā)現(xiàn)其實(shí)挺坑的,有的用‘聂受,’分割蒿秦,有的用空格,感覺(jué)我的算法并不能很好地識(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