Android自定義view實(shí)現(xiàn)評(píng)分控件

首先貼上源碼地址:https://github.com/CB-ysx/CBRatingBar

最近需要做一個(gè)星星的評(píng)分控件(可以調(diào)整進(jìn)度,進(jìn)度顏色漸變)

如圖:

image
image

一開(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

先上效果圖:

image

image

image

image

gif效果圖:

image

如何使用:

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)的圖案如下:

image

沒(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)度條效果皆辽。
繪制效果如圖:

image

接下來(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)度的星星效果,如圖:

image

設(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锥债,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子痊臭,更是在濱河造成了極大的恐慌哮肚,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件广匙,死亡現(xiàn)場(chǎng)離奇詭異允趟,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)鸦致,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)拼窥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人蹋凝,你說(shuō)我怎么就攤上這事∽芸茫” “怎么了鳍寂?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)情龄。 經(jīng)常有香客問(wèn)我迄汛,道長(zhǎng),這世上最難降的妖魔是什么骤视? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任鞍爱,我火速辦了婚禮,結(jié)果婚禮上专酗,老公的妹妹穿的比我還像新娘睹逃。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布沉填。 她就那樣靜靜地躺著疗隶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪翼闹。 梳的紋絲不亂的頭發(fā)上斑鼻,一...
    開(kāi)封第一講書(shū)人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音猎荠,去河邊找鬼坚弱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛关摇,可吹牛的內(nèi)容都是我干的荒叶。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拒垃,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼停撞!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起悼瓮,我...
    開(kāi)封第一講書(shū)人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤戈毒,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后横堡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體埋市,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年命贴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了道宅。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡胸蛛,死狀恐怖污茵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情葬项,我是刑警寧澤泞当,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站民珍,受9級(jí)特大地震影響襟士,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜嚷量,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一陋桂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蝶溶,春花似錦嗜历、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)落包。三九已至,卻和暖如春摊唇,著一層夾襖步出監(jiān)牢的瞬間咐蝇,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工巷查, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留有序,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓岛请,卻偏偏與公主長(zhǎng)得像旭寿,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子崇败,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容