Android自定义View,仿QQ显示用户等级

最近公司产品需求,有一个类似QQ等级显示的UI效果,用太阳、月亮和星星这三种图标表示用户的等级,有点类似RatingBar效果,但又有很多不一样的地方,Github上搜了一大圈,没有找到满意的;仔细想想,实现起来也比较简单,最后还是决定自己写一个算了,也练习下自定义View。

要达到的效果

截了一张TIM的效果图,其实效果差不多:

QQ等级
最终实现的效果
实现效果

我只添加了三级显示,并没有添加皇冠。因为我的QQ等级离皇冠还差的很远...
≡(▔﹏▔)≡,不过实现原理上并没有任何的区别。

实现原理简介

本Demo主要用到的知识就是自定义View中基本的View测量(onMeasure)和绘制(onDraw),加上简单的QQ等级步进算法。大致思路就是根据用户设置的等级(level字段),通过等级步进算法,计算出该等级可以用xx个太阳,xx个月亮和xx个星星表示出来;用一个list存储所有的等级图标,然后把这些小图标连续的绘制出来。

实现代码

代码仅200多行,我已经添加了详细的注释:

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;

/**
 * 类描述:
 * 创建人:luoxingyuan
 * 创建时间: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;//默认步进,就是五个星星==一个月亮,QQ是4
    private int drawableResId1;
    private int drawableResId2;
    private int drawableResId3;
    private List<String> list; //这里面的每个元素就代表一个需要绘制的小图标

    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;
    }

    //重写构造方法,让所有构造方法都指向三个参数的方法。
    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的尺寸,那么我们就给它设置一个默认值
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED://这里我的默认宽度是根据图片大小和图片数量计算出来的,具体的含义后面会说
                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高度默认设置为图片高度的两倍,如果涉及到换行,这里还需要做动态计算,我就先偷个懒
                viewHeight = sunBitmap.getHeight() * 2;
                break;
            case MeasureSpec.EXACTLY:
                viewHeight = MeasureSpec.getSize(widthMeasureSpec);
                break;
        }

        setMeasuredDimension(viewWidth, viewHeight);//把测量出来的宽高尺寸设置到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中循环取出元素,根据元素的值来判断该绘制哪种图标
            if (list.get(i).equals("日"))
            {
                //绘制太阳图标,(横坐标为图片宽度+相邻两张图片的间隔)*i+整体左边的margin值,纵坐标为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);
            }

        }

    }

    /**
     * 输入要表示的等级,计算所需要的各种图标的个数,并返回list
     * @param level
     * @return
     */
    private List<String> calculateLevel(int level)
    {

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

        int sunNum = level / (step * step);//太阳图标的个数为:等级/步进的平方(一个太阳为25级)
        int moonNum = (level - (step * step) * sunNum) / step;//月亮的个数:总等级先减去太阳表示的等级/步进值
        int starNum = level - sunNum * (step * step) - moonNum * step;//同理,计算出星星的个数


        //根据不同图标的个数添加不同的元素
        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"/><!--设置等级-->
        <attr name="step" format="integer"/><!--设置步进-->
        <attr name="drawable_1" format="reference"/><!--一级图片-->
        <attr name="drawable_2" format="reference"/><!--二级图片-->
        <attr name="drawable_3" format="reference"/><!--三级图片-->
    </declare-styleable>
</resources>

可根据自己的实际需求添加更多的自定义属性,这里就简单的添加了几个。

源码

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

使用

这里是最简单的用法,当然你可以设置自己的想要的步进值和图标。

 <com.mewlxy.qqlevelbar.QQLevelBar
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:level="111"/>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,312评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,578评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,337评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,134评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,161评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,303评论 1 280
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,761评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,421评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,609评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,450评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,504评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,194评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,760评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,836评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,066评论 1 257
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,612评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,178评论 2 341

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,315评论 25 707
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,987评论 4 60
  • 你说:“我们这两个月先不要联系了吧…”,我在屏幕这端看见这句话,呆了一下,心想:不联系就不联系,你以为我是有...
    23ef1f19a520阅读 340评论 0 0
  • 一般我们向cell中添加子视图,有两种方式 区别在于进行cell编辑时,比如cell内容向左移或者右移时,第一种方...
    青葱烈马阅读 1,333评论 0 0
  • 你说, 那次相遇, 至今难忘。 我凝视着你, 良久。 终于你的眼里有了光彩, 炫目,美丽之极。 我心里慢慢冰凉! ...
    殇雪花飞阅读 199评论 0 1