自定義View(5) -- 評(píng)分控件RatingBar

先看效果圖:


評(píng)分.gif

在我們畫這個(gè)控件之前浑吟,我們想想一下怎么實(shí)現(xiàn)這個(gè)奉件,顯示對(duì)星星的處理,我們是自己繪制還是使用圖片痴鳄?其實(shí)都是可以的辟狈,但是我們?yōu)榱烁涌焖俚耐瓿蛇@個(gè),我們選擇了使用圖片來(lái)完成夏跷。先自定義屬性,在這個(gè)例子中我定義如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="RatingBar">
        <!--未選中引用-->
        <attr name="starNormal" format="reference" />
        <!--選中引用-->
        <attr name="starFocus" format="reference" />
        <!--最大的分?jǐn)?shù)-->
        <attr name="gradeNumber" format="integer" />
        <!--當(dāng)前的分?jǐn)?shù)-->
        <attr name="currentGrade" format="integer" />
        <!--星星之間的間距-->
        <attr name="statPadding" format="dimension" />
    </declare-styleable>
</resources>

我們?cè)诔跏蓟臅r(shí)候獲取相關(guān)數(shù)據(jù):

 private Bitmap mStarFocusBitmap, mStarNormalBitmap;
    private int mGradeNumber;//最大分?jǐn)?shù)
    private int mCurrentGrade;//當(dāng)前分?jǐn)?shù)  1開始的  最少為1分
    private int mStarPadding;//間距

    public RatingBar(Context context) {
        this(context, null);
    }

    public RatingBar(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RatingBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);


        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.RatingBar);
        int starNormalId = array.getResourceId(R.styleable.RatingBar_starNormal, 0);
        if (starNormalId == 0) {
            throw new RuntimeException("請(qǐng)?jiān)O(shè)置屬性 starNormal ");
        }

        mStarNormalBitmap = BitmapFactory.decodeResource(getResources(), starNormalId);

        int starFocusId = array.getResourceId(R.styleable.RatingBar_starFocus, 0);
        if (starFocusId == 0) {
            throw new RuntimeException("請(qǐng)?jiān)O(shè)置屬性 starFocus ");
        }

        mStarFocusBitmap = BitmapFactory.decodeResource(getResources(), starFocusId);
        mGradeNumber = array.getInt(R.styleable.RatingBar_gradeNumber, 0);
        mStarPadding = array.getDimensionPixelOffset(R.styleable.RatingBar_statPadding, DisplayUtil.dip2px(context, 5));
        mCurrentGrade = array.getInt(R.styleable.RatingBar_currentGrade, 1);

        array.recycle();
    }
}

接下來(lái)我們?cè)O(shè)置寬高明未,本例中就直接寫了槽华,在實(shí)際開發(fā)中如有需要在修改

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 高度  一張圖片的高度
        int height = mStarFocusBitmap.getHeight() + getPaddingTop() + getPaddingBottom();
        int width = mStarFocusBitmap.getWidth() * mGradeNumber
                + getPaddingLeft() + getPaddingRight()
                + mStarPadding * (mGradeNumber - 1);
        setMeasuredDimension(width, height);
    }

接下來(lái)調(diào)用onDraw函數(shù)繪制:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        for (int i = 0; i < mGradeNumber; i++) {
            if (i < mCurrentGrade) {
                canvas.drawBitmap(mStarFocusBitmap,
                        getPaddingLeft() + i * mStarFocusBitmap.getWidth() + i * mStarPadding, getPaddingTop(), null);
            } else {
                canvas.drawBitmap(mStarNormalBitmap,
                        getPaddingLeft() + i * mStarFocusBitmap.getWidth() + i * mStarPadding, getPaddingTop(), null);
            }
        }
    }

我們進(jìn)行測(cè)試:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
   >


    <com.zzw.customview.view.RatingBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        app:currentGrade="2"
        app:gradeNumber="5"
        app:statPadding="10dp"
        app:starFocus="@mipmap/star_selected"
        app:starNormal="@mipmap/star_normal"
        />


</RelativeLayout>

效果圖如下:

這時(shí)候只是一個(gè)靜態(tài)的,點(diǎn)擊的時(shí)候并沒(méi)有變化趟妥,我們接下來(lái)進(jìn)行onTouchEvent監(jiān)聽猫态,然后進(jìn)行改變值重繪:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                //計(jì)算下標(biāo)
               int pos = (int) (x / (mStarFocusBitmap.getWidth() + mStarPadding));
              if (pos < 0) pos = 0;
              if (pos >= mGradeNumber)  pos = mGradeNumber - 1;
                mCurrentGrade = pos + 1;
                invalidate();
                break;
        }
        return true;
    }

我們?yōu)槭裁匆?code>1呢?因?yàn)槲覀?code>mCurrentGrade表示的是評(píng)分的分?jǐn)?shù)披摄,所以用下標(biāo)加1亲雪,這個(gè)時(shí)候我們效果就達(dá)到了,但是我們不要忘記優(yōu)化疚膊。
因?yàn)槲覀兎祷亓?code>true,所以在滑動(dòng)的時(shí)候一直接受到move的事件义辕,就一直進(jìn)行調(diào)用了invalidate重繪 ,我們上次也說(shuō)過(guò)invalidate會(huì)做很多事情寓盗,所以我們就要減少重繪的此時(shí)灌砖,我們應(yīng)該加上判斷:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                //計(jì)算下標(biāo)
              int pos = (int) (x / (mStarFocusBitmap.getWidth() + mStarPadding));
              if (pos < 0) pos = 0;
              if (pos >= mGradeNumber)  pos = mGradeNumber - 1;
              if (pos != mCurrentGrade - 1) {//分?jǐn)?shù)-1 等于 坐標(biāo)pos
                    mCurrentGrade = pos + 1;
                    invalidate();
                }
                break;
        }
        return true;
    }

這樣的畫就優(yōu)雅了很多璧函,當(dāng)然也不要忘記bitmap的回收,我們將暴露出函數(shù)來(lái)給使用者在activityonDestory生命周期調(diào)用基显。

/**
     * 回收
     */
    public void recycle() {
        if (mStarFocusBitmap != null)
            mStarFocusBitmap.recycle();

        if (mStarNormalBitmap != null)
            mStarNormalBitmap.recycle();

        mStarFocusBitmap=null;
        mStarNormalBitmap=null;
    }

activity里面調(diào)用:

 @Override
    protected void onDestroy() {
        super.onDestroy();
        mRatingBar.recycle();
    }

這樣的話對(duì)于強(qiáng)迫癥患者心里就舒坦多了蘸吓。當(dāng)然,我也是一個(gè)強(qiáng)迫癥換患者撩幽。但是有時(shí)候我們也不能一定要追求極致库继,比如在獲取位置的時(shí)候,我有一個(gè)想法窜醉,就是根據(jù)星星的范圍精確的獲取當(dāng)前的位置,比如下面這樣:


當(dāng)時(shí)我就寫了一個(gè)小算法:

  /**
     * 計(jì)算位置
     *
     * @param x
     * @return
     */
    private int comPos(float x) {

        int startX = getPaddingLeft();
        if (x < startX) {
            return 0;
        }

        if (x > getWidth() - getPaddingRight()) {
            return mGradeNumber;
        }

        int pos = 0;
        for (int i = 0; i < mGradeNumber; i++) {
            int endX;
            if (i == 0 || i == mGradeNumber - 1) {//第0個(gè)位置的后面的x
                endX = startX + mStarFocusBitmap.getWidth() + mStarPadding / 2;
            } else {
                endX = startX + mStarFocusBitmap.getWidth() + mStarPadding;
            }
            if (x < endX) {
                pos = i;
                break;
            }
            startX = endX;
        }
        return pos;
    }

這樣的畫就能夠準(zhǔn)確的拿到星星的位置宪萄,但是這樣的弊端就是一個(gè)for循環(huán),將會(huì)耗費(fèi)更多的時(shí)間酱虎,所以最后還是改為了一個(gè)模糊的計(jì)算位置的方式雨膨。所以有時(shí)候也不能太強(qiáng)迫自己,不要鉆牛角尖读串。
這篇文章結(jié)束了聊记,希望對(duì)大家有所幫助!

下載鏈接:https://github.com/ChinaZeng/CustomView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市恢暖,隨后出現(xiàn)的幾起案子排监,更是在濱河造成了極大的恐慌,老刑警劉巖杰捂,帶你破解...
    沈念sama閱讀 216,651評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舆床,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡嫁佳,警方通過(guò)查閱死者的電腦和手機(jī)挨队,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蒿往,“玉大人盛垦,你說(shuō)我怎么就攤上這事∪柯” “怎么了腾夯?”我有些...
    開封第一講書人閱讀 162,931評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蔬充。 經(jīng)常有香客問(wèn)我蝶俱,道長(zhǎng),這世上最難降的妖魔是什么饥漫? 我笑而不...
    開封第一講書人閱讀 58,218評(píng)論 1 292
  • 正文 為了忘掉前任榨呆,我火速辦了婚禮,結(jié)果婚禮上庸队,老公的妹妹穿的比我還像新娘愕提。我一直安慰自己馒稍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,234評(píng)論 6 388
  • 文/花漫 我一把揭開白布浅侨。 她就那樣靜靜地躺著纽谒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪如输。 梳的紋絲不亂的頭發(fā)上鼓黔,一...
    開封第一講書人閱讀 51,198評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音不见,去河邊找鬼澳化。 笑死,一個(gè)胖子當(dāng)著我的面吹牛稳吮,可吹牛的內(nèi)容都是我干的缎谷。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼灶似,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼列林!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起酪惭,我...
    開封第一講書人閱讀 38,926評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤希痴,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后春感,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砌创,經(jīng)...
    沈念sama閱讀 45,341評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,563評(píng)論 2 333
  • 正文 我和宋清朗相戀三年鲫懒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嫩实。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,731評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窥岩,死狀恐怖舶赔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谦秧,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評(píng)論 5 343
  • 正文 年R本政府宣布撵溃,位于F島的核電站疚鲤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缘挑。R本人自食惡果不足惜集歇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,036評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望语淘。 院中可真熱鬧诲宇,春花似錦际歼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至纺荧,卻和暖如春旭愧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背宙暇。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工输枯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人占贫。 一個(gè)月前我還...
    沈念sama閱讀 47,743評(píng)論 2 368
  • 正文 我出身青樓桃熄,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親型奥。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瞳收,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,629評(píng)論 2 354

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