Android自定義View,仿QQ顯示用戶等級

最近公司產(chǎn)品需求,有一個類似QQ等級顯示的UI效果,用太陽、月亮和星星這三種圖標(biāo)表示用戶的等級,有點類似RatingBar效果,但又有很多不一樣的地方,Github上搜了一大圈,沒有找到滿意的;仔細(xì)想想,實現(xiàn)起來也比較簡單,最后還是決定自己寫一個算了,也練習(xí)下自定義View。

要達(dá)到的效果

截了一張TIM的效果圖,其實效果差不多:

QQ等級
最終實現(xiàn)的效果
實現(xiàn)效果

我只添加了三級顯示,并沒有添加皇冠。因為我的QQ等級離皇冠還差的很遠(yuǎn)...
≡(▔﹏▔)≡,不過實現(xiàn)原理上并沒有任何的區(qū)別。

實現(xiàn)原理簡介

本Demo主要用到的知識就是自定義View中基本的View測量(onMeasure)和繪制(onDraw),加上簡單的QQ等級步進(jìn)算法。大致思路就是根據(jù)用戶設(shè)置的等級(level字段),通過等級步進(jìn)算法,計算出該等級可以用xx個太陽,xx個月亮和xx個星星表示出來;用一個list存儲所有的等級圖標(biāo),然后把這些小圖標(biāo)連續(xù)的繪制出來。

實現(xiàn)代碼

代碼僅200多行,我已經(jīng)添加了詳細(xì)的注釋:

package com.mewlxy.qqlevelbar;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

import java.util.ArrayList;
import java.util.List;

/**
 * 類描述:
 * 創(chuàng)建人:luoxingyuan
 * 創(chuàng)建時間:2017/8/20 21:45
 * 修改人:luoxingyuan
 * 修改時間:2017/8/20 21:45
 * 修改備注:
 */

public class QQLevelBar extends View
{

    private Context context;
    private int viewWidth;
    private int viewHeight;
    private int margin;
    private Bitmap sunBitmap;
    private Bitmap moonBitmap;
    private Bitmap starBitmap;
    private Paint bitmapPaint;
    private int level;
    private int step = 5;//默認(rèn)步進(jìn),就是五個星星==一個月亮,QQ是4
    private int drawableResId1;
    private int drawableResId2;
    private int drawableResId3;
    private List<String> list; //這里面的每個元素就代表一個需要繪制的小圖標(biāo)

    public int getLevel()
    {
        return level;
    }

    public void setLevel(int level)
    {
        this.level = level;
    }

    public int getStep()
    {
        return step;
    }

    public void setStep(int step)
    {
        this.step = step;
    }

    public int getDrawableResId1()
    {
        return drawableResId1;
    }

    public void setDrawableResId1(int drawableResId1)
    {
        this.drawableResId1 = drawableResId1;
    }

    public int getDrawableResId2()
    {
        return drawableResId2;
    }

    public void setDrawableResId2(int drawableResId2)
    {
        this.drawableResId2 = drawableResId2;
    }

    public int getDrawableResId3()
    {
        return drawableResId3;
    }

    public void setDrawableResId3(int drawableResId3)
    {
        this.drawableResId3 = drawableResId3;
    }

    //重寫構(gòu)造方法,讓所有構(gòu)造方法都指向三個參數(shù)的方法。
    public QQLevelBar(Context context)
    {
        this(context, null, 0);
    }

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

    public QQLevelBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        this.context = context;

        //獲取自定義屬性樣式列表
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.qq_level_view);

        level = typedArray.getInt(R.styleable.qq_level_view_level, 0);
        step = typedArray.getInt(R.styleable.qq_level_view_step, 5);
        drawableResId1 = typedArray.getResourceId(R.styleable.qq_level_view_drawable_1, R.drawable.icon_sun_checked);
        drawableResId2 = typedArray.getResourceId(R.styleable.qq_level_view_drawable_2, R.drawable.icon_moon_checked);
        drawableResId3 = typedArray.getResourceId(R.styleable.qq_level_view_drawable_3, R.drawable.icon_star_checked);

        //獲取完了之后別忘了回收
        typedArray.recycle();


        init();
    }


    //初始化一些資源
    private void init()
    {
        sunBitmap = BitmapFactory.decodeResource(getResources(), drawableResId1);
        moonBitmap = BitmapFactory.decodeResource(getResources(), drawableResId2);
        starBitmap = BitmapFactory.decodeResource(getResources(), drawableResId3);
        bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        list = calculateLevel(level);


    }

    @Override//View測量
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        //獲取寬度和高度的測量模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        switch (widthMode)
        {
            //如果使用者沒有明確指定View的尺寸,那么我們就給它設(shè)置一個默認(rèn)值
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED://這里我的默認(rèn)寬度是根據(jù)圖片大小和圖片數(shù)量計算出來的,具體的含義后面會說
                viewWidth = (sunBitmap.getWidth() + sunBitmap.getWidth() / 3) * list.size() + sunBitmap.getWidth() / 2;
                break;
            case MeasureSpec.EXACTLY://如果用戶明確指定了尺寸,就按照用戶指定的來
                viewWidth = MeasureSpec.getSize(widthMeasureSpec);
                break;
        }
        switch (heightMode)
        {
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED://View高度默認(rèn)設(shè)置為圖片高度的兩倍,如果涉及到換行,這里還需要做動態(tài)計算,我就先偷個懶
                viewHeight = sunBitmap.getHeight() * 2;
                break;
            case MeasureSpec.EXACTLY:
                viewHeight = MeasureSpec.getSize(widthMeasureSpec);
                break;
        }

        setMeasuredDimension(viewWidth, viewHeight);//把測量出來的寬高尺寸設(shè)置到view中去,這個很重要

        margin = sunBitmap.getHeight() / 2;//上下左右的margin值
    }


    @Override
    protected void onDraw(Canvas canvas)//在這里繪制想要的效果
    {
        int bitmapMargin = sunBitmap.getWidth() / 3;//圖片之間的橫向間隔

        for (int i = 0; i < list.size(); i++)
        {

            //從list中循環(huán)取出元素,根據(jù)元素的值來判斷該繪制哪種圖標(biāo)
            if (list.get(i).equals("日"))
            {
                //繪制太陽圖標(biāo),(橫坐標(biāo)為圖片寬度+相鄰兩張圖片的間隔)*i+整體左邊的margin值,縱坐標(biāo)為view的高度/2-整體的margin值,下面的繪制同理
                canvas.drawBitmap(sunBitmap, (sunBitmap.getWidth() + bitmapMargin) * i + sunBitmap.getWidth() / 2,
                        viewHeight / 2 - margin, bitmapPaint);
            } else if (list.get(i).equals("月"))
            {
                canvas.drawBitmap(moonBitmap, (moonBitmap.getWidth() + bitmapMargin) * i + moonBitmap.getWidth() / 2,
                        viewHeight / 2 - margin, bitmapPaint);
            } else
            {
                canvas.drawBitmap(starBitmap, (starBitmap.getWidth() + bitmapMargin) * i + starBitmap.getWidth() / 2,
                        viewHeight / 2 - margin, bitmapPaint);
            }

        }

    }

    /**
     * 輸入要表示的等級,計算所需要的各種圖標(biāo)的個數(shù),并返回list
     * @param level
     * @return
     */
    private List<String> calculateLevel(int level)
    {

        List<String> list = new ArrayList<>();

        int sunNum = level / (step * step);//太陽圖標(biāo)的個數(shù)為:等級/步進(jìn)的平方(一個太陽為25級)
        int moonNum = (level - (step * step) * sunNum) / step;//月亮的個數(shù):總等級先減去太陽表示的等級/步進(jìn)值
        int starNum = level - sunNum * (step * step) - moonNum * step;//同理,計算出星星的個數(shù)


        //根據(jù)不同圖標(biāo)的個數(shù)添加不同的元素
        for (int i = 0; i < sunNum; i++)
        {
            list.add("日");
        }

        for (int i = 0; i < moonNum; i++)
        {
            list.add("月");
        }

        for (int i = 0; i < starNum; i++)
        {

            list.add("星");
        }


        return list;
    }
}

自定義屬性
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="qq_level_view">
        <attr name="level" format="integer"/><!--設(shè)置等級-->
        <attr name="step" format="integer"/><!--設(shè)置步進(jìn)-->
        <attr name="drawable_1" format="reference"/><!--一級圖片-->
        <attr name="drawable_2" format="reference"/><!--二級圖片-->
        <attr name="drawable_3" format="reference"/><!--三級圖片-->
    </declare-styleable>
</resources>

可根據(jù)自己的實際需求添加更多的自定義屬性,這里就簡單的添加了幾個。

源碼

https://github.com/lxygithub/QQLevelBar
可下載源碼自行改造出自己想要的效果,如果能給個star那就更好了。
(~ ̄▽ ̄)~

使用

這里是最簡單的用法,當(dāng)然你可以設(shè)置自己的想要的步進(jìn)值和圖標(biāo)。

 <com.mewlxy.qqlevelbar.QQLevelBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:level="111"/>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,334評論 2 377

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,703評論 25 708
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,162評論 4 61
  • 你說:“我們這兩個月先不要聯(lián)系了吧…”,我在屏幕這端看見這句話,呆了一下,心想:不聯(lián)系就不聯(lián)系,你以為我是有...
    23ef1f19a520閱讀 351評論 0 0
  • 一般我們向cell中添加子視圖,有兩種方式 區(qū)別在于進(jìn)行cell編輯時,比如cell內(nèi)容向左移或者右移時,第一種方...
    青蔥烈馬閱讀 1,335評論 0 0
  • 你說, 那次相遇, 至今難忘。 我凝視著你, 良久。 終于你的眼里有了光彩, 炫目,美麗之極。 我心里慢慢冰涼! ...
    殤雪花飛閱讀 209評論 0 1