Android自定义带消息提醒控件

相信大家都玩过各类社交软件,当有消息提示的时候会有消息提醒以便用户通知用户有消息了

安卓里面能实现这种效果有2种方式

1 可以用个framelayout来控制位置

2 写个自定义View来专门为这个需求服务(今天要讲的重点)

3 网上专门有一个BadgeView来做这事情(这东西我看了一下源码,大概思路就是 该控件继承了TextView,经过各种处理之后把要设置的view的父layout改成了FrameLayout,,其实是跟第一点思路有点类似的,但是这有一个问题,当你原来的父控件是相对布局也就是RelaviteLayout的时候 用这个控件就6了 位置全乱了,欢迎尝试,这东西百度一下整页都是 小弟就不放链接了昂)


小弟文采有限就不bb太多了 666

直接开始吧


分大概x个步骤

1 自定义属性(没自定义属性还叫自定义View??)

测量该控件的宽高

3 测量控件里头图片的大小,位置

4 测量消息提醒的圆或者其他奇形怪状的消息提醒x形状的位置

测量消息提醒字体的位置

嗯 x=5;


首先自定义View肯定有自定义属性,不然就没逼格了哈

属性如下:

res-values-attr.xml里头

<declare-styleable name="BadgeView">
        <attr name="badgeText" format="string"></attr>
        <attr name="badgeBitmap" format="reference"></attr>
        <attr name="badgeColor" format="color"></attr>
        <attr name="badgeTextColor" format="color"></attr>
        <attr name="badgeTextSize" format="dimension"></attr>
        <attr name="badgeRadio" format="float"></attr>
        <attr name="badgePosition">
            <enum name="center" value="0"></enum>
            <enum name="left_top" value="1"></enum>
            <enum name="left_vertical" value="2"></enum>
            <enum name="left_bottom" value="3"></enum>
            <enum name="right_top" value="4"></enum>
            <enum name="right_vertical" value="5"></enum>
            <enum name="right_bottom" value="6"></enum>
            <enum name="top_horizatal" value="7"></enum>
            <enum name="bottom_horizatal" value="8"></enum>
        </attr>
    </declare-styleable>
badge means 标签 

属性就不介绍了 见名思意


然后通过布局文件设置该属性:

 <com.example.app.BadgeView
        android:id="@+id/view"
        android:layout_width="150dp"
        android:layout_height="50dp"
        android:background="#a06c"
        custom:badgeBitmap="@drawable/ic_launcher"
        custom:badgeColor="#a06c"
        custom:badgePosition="right_top"
        custom:badgeRadio="0.18"
        custom:badgeText="99"
        custom:badgeTextColor="@android:color/white"
        custom:badgeTextSize="13sp" />
记得要在命名空间加上  xmlns:custom="http://schemas.android.com/apk/res/com.example.app" (直接复制自动生成的 改一下就好了 最后的是你的项目的包名)


然后通过代码取得设置的属性:

先声明全局变量:

    private String badgeText;//标签里面的字体
    private Bitmap badgeBitmap;//要显示的位图
    private Paint textPaint;//字体画笔
    private Paint badgePaint;//标签画笔
    private Rect textRect;//测量字体宽高的类
    private int badgeTextSize;//字体大小
    private int badgeTextColor;//字体颜色
    private int badgeColor;//标签的颜色
    private int badgePosition;//标签的位置
    private float radius;//标签的半径
    private float badgeRadio = 0.3f;//标签对于整个view的比例
    private boolean isHasBadge = false;//是否需要标签


构造方法里面:

public BadgeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        // TODO Auto-generated constructor stub
        TypedArray a = context.obtainStyledAttributes(attrs,
                R.styleable.BadgeView);
        int count = a.getIndexCount();
        for (int i = 0; i < count; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case R.styleable.BadgeView_badgeBitmap:
                badgeBitmap = BitmapFactory.decodeResource(getResources(),
                        a.getResourceId(attr, R.drawable.ic_launcher));
                    break;
                case R.styleable.BadgeView_badgeColor:
                badgeColor = a.getColor(attr, Color.RED);
                    break;
                case R.styleable.BadgeView_badgePosition:
                badgePosition = a.getInt(attr, 0);
                    break;
                case R.styleable.BadgeView_badgeText:
                badgeText = a.getString(attr);
                    break;
                case R.styleable.BadgeView_badgeTextColor:
                badgeTextColor = a.getColor(attr, Color.WHITE);
                    break;
                case R.styleable.BadgeView_badgeTextSize:
                badgeTextSize = a.getDimensionPixelSize(attr, 0);
                    break;
                case R.styleable.BadgeView_badgeRadio:
                badgeRadio = a.getFloat(attr, 0.3f);
                    break;
            }
        }
        a.recycle();
        if (badgeText != null) {
            initTextPaint();
        }
        badgePaint = new Paint();
        badgePaint.setColor(badgeColor);
        badgePaint.setAntiAlias(true);
    }

initTextPaint()方法就是初始化字体画笔:


    private void initTextPaint() {
        textPaint = new Paint();
        textRect = new Rect();
        textPaint.setColor(badgeTextColor);
        textPaint.setTextSize(badgeTextSize);
        textPaint.getTextBounds(badgeText, 0, badgeText.length(), textRect);
        textPaint.setAntiAlias(true);
    }

初始化完成以后,开始测量该view的宽高的,重写onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // TODO Auto-generated method stub
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        int width, height;
        if (specMode == MeasureSpec.EXACTLY) {
            width = specSize;
        } else {
            width = badgeBitmap.getWidth();
        }
        specMode = MeasureSpec.getMode(heightMeasureSpec);
        specSize = MeasureSpec.getSize(heightMeasureSpec);
        if (specMode == MeasureSpec.EXACTLY) {
            height = specSize;
        } else {
            height = badgeBitmap.getHeight();
        }
        setMeasuredDimension(width, height);
        calculateScaleBitmap();
    }

代码解释:

首先获得模式跟值(系统默认)

如果模式是 MeasureSpec.EXACTLY 那么也就是说你已经设置了Layout_width,height为准确的数值或者是match_parent 否则 则按照bitmap的大小作为该view的大小
最后把你测量好的值设上去 setMeasuredDimension(width, height); 

测量好View的宽高之后顺便把bitmap也测量了:

    private void calculateScaleBitmap() {
        // TODO Auto-generated method stub
        int min = Math.min(getMeasuredHeight(), getMeasuredHeight());
        if (badgeBitmap.getWidth() > getMeasuredWidth()
                || badgeBitmap.getHeight() > getMeasuredHeight()) {
            badgeBitmap = Bitmap
                    .createScaledBitmap(badgeBitmap, min, min, true);
        }
    }

代码解释: 获得宽高最短的一边作为标准:如果bitmap宽大于控件的宽 或者 高大于控件的高 则直接按照控件最小的边做缩放

这么一来我们的bitmap宽高也设置好了(默认或者按照以上做缩放)


好了就画出来呗 对吧 ^_^

咋画? 重写onDraw(Canvas canvas)方法呗

    @Override
    protected void onDraw(Canvas canvas) {
        // TODO Auto-generated method stub
        drawBadgeBitmap(canvas);
        if (isHasBadge)
            drawBadgeText(canvas);
    }
我们先把目光放在drawBitmap(Canvas canvas)方法上,很简单:

  private void drawBadgeBitmap(Canvas canvas) {
        // TODO Auto-generated method stub
        int left = getWidth() / 2 - badgeBitmap.getWidth() / 2
                + getPaddingLeft() - getPaddingRight();
        int top = getHeight() / 2 - badgeBitmap.getHeight() / 2
                + getPaddingTop() - getPaddingBottom();
        canvas.drawBitmap(badgeBitmap, left, top, null);
    }

虽然简单 也解释一下吧。。

首先测量该bitmap要画在哪个位置 (刚刚的是测量大小哦 现在才是画到View里面去哦)

首先测量该bitmap到底要画在什么位置(我这里是居中), 所以要测量出左边相当于控件来说是什么位置 上边相对于控件来说是什么位置 因为我们刚刚已经测量了bitmap的宽高 所以下边右边就直接按照bitmap的高 宽来确定了

如果要居中 左边当然就是: 控件的宽/2-bitmap的宽/2 (不懂的拿笔拿纸算一下,数学36分表示这点数学题完全没难度,2333)

    那么上边呢? 如法炮制,控件的高/2-bitmap的高/2 (同上)

最后用canvas.drawBItmap(Bitmap src,int left,int top,Paint paint)画出位图,至于最后一个为什么是Null? 小弟学艺不精 只知道null也能画出bitmap就对了^_^

好了 这么一来图片总算画好了


然后再来画我们的标记, 标记这个东西嘛。。我这里是一个红色圆 其他的形状思路也是一样的,先来讲讲思路吧

Q1 这个圆的面积是多大(重点 直接影响体验)

Q2 这个圆是啥颜色 (easy 上面不是有个标记画笔么)

Q3 这个圆在什么位置(重点 算一下就好了)

那么下面我们一个一个来解决:

A1:

圆的面积=x; x=piR平方对吧 Java已经提供了Pi 我们只要算r=多少就ok了。

好几种写法 可以写死 比如圆是整个面积的10分1 8分1什么的 我一开始也是这样写的 看着还可以  但是这写法嘛 好像不太灵活 万一大小不喜欢还tm要去改源码?不干,果断不干。 所以我在自定义属性的xml里加了一个badgeRadio属性 也就是可以自己动态设置面积 其实也就是半径拉 那么这个半径如何算呢,这里以圆的面积是位图总面积的30%为例:

    private int calculateRadius() {
        // TODO Auto-generated method stub
        int totalArea = badgeBitmap.getWidth() * badgeBitmap.getHeight();
        return (int) Math.sqrt(totalArea * badgeRadio / Math.PI);
    }


其中badgeRadio=0.3
totalArea 总面积=位图的总面积 正方形的面积不用说了吧。。w * h

半径: 公式:pi*r²=位图w*h*0.3 那么r²=w*h*0.3/pi  那么r=开根号前者 

r就出来了 r出来了就tm好办了 直接画圆就好了6666

等等 这圆该画在什么位置。。。。

嗯对 下面就来解决这个问题

在自定义属性里面我们看到了一个bradePosition来设置圆到底在哪 分别是中间 左上 左中 左下,右上,右中,右下,顶中,底中,那么我们就来算这些位置

   
    private void drawBadgeText(Canvas canvas) {
        // TODO Auto-generated method stub
        radius = (float) calculateRadius();
        float cx = 0;
        float cy = 0;
        switch (badgePosition) {
            case 0:
            cx = getWidth() / 2;
            cy = getHeight() / 2;
                break;
            case 1:
            cx = getPaddingLeft() + radius;
            cy = getPaddingTop() + radius;
                break;
            case 2:
            cx = getPaddingLeft() + radius;
            cy = getHeight() / 2;
                break;
            case 3:
            cx = getPaddingLeft() + radius;
            cy = getHeight() - getPaddingBottom() - radius;
                break;
            case 4:
            cx = getWidth() - getPaddingRight() - radius;
            cy = getPaddingTop() + radius;
                break;
            case 5:
            cx = getWidth() - getPaddingRight() - radius;
            cy = getHeight() / 2;
                break;
            case 6:
            cx = getWidth() - getPaddingRight() - radius;
            cy = getHeight() - getPaddingBottom() - radius;
                break;
            case 7:
            cx = getWidth() / 2;
            cy = getPaddingTop() + radius;
                break;
            case 8:
            cx = getWidth() / 2;
            cy = getHeight() - getPaddingBottom() - radius;
                break;
        }
        canvas.drawCircle(cx, cy, radius, badgePaint);
        canvas.drawText(badgeText, (float) (cx - textRect.width() / 2.0f),
                (float) (cy + textRect.height() / 2.0f), textPaint);
    }
获得半径 判断位置 画出来 圆的位置一旦出来了 字体的位置自然也就出来了 在圆的中间嘛 算法跟刚的位图一样的

那么这个自定义view貌似就已经全部出来了亲,图 标记 标记里面的字体 都出来了。下面就向外公布一些方法能设置所需属性就ok了 就不贴出来了


不过这里有一个 setPosition的时候 你总不能要人家setPosition(1),setPosition(2)这样吧 鬼知道代表啥阿,所以我这里写了个枚举来表达 更直观一些

    public enum BadgeType {
        CENTER(0), LEFTTOP(1), LEFTVERTICAL(2), LEFTBOTTOM(3), RIGHTTOP(4), RIGHTVERTICAL(
                5), RIGHTBOTTOM(6), TOPHORIZATAL(7), BOTTOMHORIZATAL(8);
        
        private final int value;
        
        BadgeType(int value) {
            this.value = value;
        }
        
        public int getValue() {
            return value;
        }
    }

没啥难点  枚举的赋值 百度一下成吨

效果图 

源码传送门 











  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值