Android自定义小红点BadgeView

想全局统一个小红点样式,总是改了这个忘了其他的,而且小红点格式各样,总是满足不了自己的需求,所以心血来潮自己自定义View onDraw了一个。

可前往查看GitHub源码.

效果就是这样....

Android自定义小红点BadgeView_第1张图片
BadgeView_preview.gif

一定要记得在attrs.xml 项目中添加



    
        
        
        
        
        

        
        
        

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
        
    

主要代码如下:

public class BadgeView extends View {

    ////可设置部分 start///////////////////////////////////////////////
    // 主体部分的设置  icon
    private int iconSrc;
    private float iconWidth;
    private float iconHeight;
    // 没有icon 就是文字描述了; icon的优先级比text高
    private String text;
    private int textColor;
    private float textSize;

    // 未读数; 在显示的时候 未读数默认显示形式9/23/99+
    private int badgeNum;
    private int badgeBackgroundColor;
    private int badgeNumColor;
    private float badgeNumSize;
    // 是否显示数字, 默认显示小红点
    private boolean showNum;
    // 不显示数字时, 小红点的大小, 不包括边线
    private float badgeRedSize;
    // 边线, 有些小红点外边有白边, 若是设置了宽度,则会添加边线; 边线算在Badge整个的大小当中
    private float badgeBorderWidth;
    private int badgeBorderColor;
    // 有些设计要求未读前面加"+", (至少我们设计师这么设计) 显示成 +1/+34/+99
    private String badgeNumPre;
    // badge的左下角 相对于 text/icon 右上角的相对位置,
    // 默认是( badgeHeight/2 ), 正好覆盖一个角
    private float badgeBottom;
    private float badgeLeft;
    // 是否自己设置了
    private boolean hasBadgeBottomAttr;
    private boolean hasBadgeLeftAttr;

    // view设置的padding
    private float viewPaddingLeft;
    private float viewPaddingTop;
    private float viewPaddingRight;
    private float viewPaddingBootom;
    ////可设置部分 end///////////////////////////////////////////////

    // 小红点真实大小 比 文本 的margin(不包括白边)
    private static final int BADGE_TEXT_MARGIN_LEFT = 10;
    private static final int BADGE_TEXT_MARGIN_TOP = 6;
    private static final int BADGE_TEXT_MARGIN_RIGHT = 10;
    private static final int BADGE_TEXT_MARGIN_BOOTOM = 6;

    // 可以设置padding
    private static final int VIEW_PADDING = 0;

    ////以下是辅助变量///////////////////////////////////////////////
    // 整个View的真实大小
    private float viewHeight;
    private float viewWidth;
    // 内容所占的大小, 内容居中
    private float viewMinHeight;
    private float viewMinWidth;
    // 小红点有向右突出部分,为保证主体部分水平居中, 需要设置两边的margin
    private float mainMarginHorizontal;
    // 小红点有向上突出部分,就算没有未读数,也需要预留出位置, 设置Top即可
    private float mainMarginTop;
    // 描述文字或者icon的宽高
    private float mainWidth;
    private float mainHeight;
    // badge的整体宽高
    private float badgeHeight;
    private float badgeWidth;
    // badgeNum/小红点 的真实宽高
    private float badgeNumHeight;
    private float badgeNumWidth;
    // icon
    private Bitmap iconBitmap;
    // 未读数显示的文案; 未读数默认显示形式9/23/99+
    private String showUneadText;

    // 画笔
    private Paint contentPaint;
    private TextPaint textPaint;
    private TextPaint badgeNumPaint;

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

    public BadgeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public BadgeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.BadgeView);

        iconSrc = array.getResourceId(R.styleable.BadgeView_iconSrc, 0);
        float iconSize = array.getDimension(R.styleable.BadgeView_iconSize, dip2px(30));
        iconWidth = array.getDimension(R.styleable.BadgeView_iconWidth, iconSize);
        iconHeight = array.getDimension(R.styleable.BadgeView_iconHeight, iconSize);

        text = array.getString(R.styleable.BadgeView_text);
        if (TextUtils.isEmpty(text)) {
            text = "Hello World";
        }
        textColor = array.getColor(R.styleable.BadgeView_textColor, Color.BLACK);
        textSize = array.getDimension(R.styleable.BadgeView_textSize, sp2px(16));

        badgeNum = array.getInteger(R.styleable.BadgeView_badgeNum, 0);
        badgeBackgroundColor = array.getColor(R.styleable.BadgeView_badgeBackgroundColor, Color.rgb(0xFF, 0x76, 0x90));
        badgeNumColor = array.getColor(R.styleable.BadgeView_badgeNumColor, Color.WHITE);
        badgeNumSize = array.getDimension(R.styleable.BadgeView_badgeNumSize, sp2px(10));
        badgeNumSize = array.getDimension(R.styleable.BadgeView_badgeNumSize, sp2px(10));
        showNum = array.getBoolean(R.styleable.BadgeView_showNum, true);
        badgeRedSize = array.getDimension(R.styleable.BadgeView_badgeRedSize, dip2px(8));
        badgeBorderColor = array.getColor(R.styleable.BadgeView_badgeBorderColor, Color.WHITE);
        badgeBorderWidth = array.getDimension(R.styleable.BadgeView_badgeBorderWidth, 0);
        if (badgeBorderWidth < 0) {
            badgeBorderWidth = 0;
        }
        badgeNumPre = array.getString(R.styleable.BadgeView_badgeNumPre);

        // 初始化badgeNum的画笔
        badgeNumPaint = new TextPaint();
        badgeNumPaint.setAntiAlias(true);
        badgeNumPaint.setColor(badgeNumColor);
        badgeNumPaint.setTextSize(badgeNumSize);
        badgeNumPaint.setTextAlign(Paint.Align.CENTER);
        // 计算 未读数的高度
        String minBadge = getUnreadText(0);
        Rect minBadgeRect = new Rect();
        badgeNumPaint.getTextBounds(minBadge, 0, minBadge.length(), minBadgeRect);
        // 计算badge的高度
        badgeNumHeight = minBadgeRect.height();
        badgeHeight = badgeNumHeight + BADGE_TEXT_MARGIN_TOP + BADGE_TEXT_MARGIN_BOOTOM + badgeBorderWidth * 2;
        // 限制设置小红点的大小不能超过数字显示模式; 显示在文字模式大小的左下角
        if (badgeRedSize > badgeNumHeight + BADGE_TEXT_MARGIN_TOP + BADGE_TEXT_MARGIN_BOOTOM) {
            badgeRedSize = badgeNumHeight + BADGE_TEXT_MARGIN_TOP + BADGE_TEXT_MARGIN_BOOTOM;
        }
        // 获取位置
        hasBadgeBottomAttr = array.hasValue(R.styleable.BadgeView_badgeBottom);
        hasBadgeLeftAttr = array.hasValue(R.styleable.BadgeView_badgeLeft);
        badgeBottom = array.getDimension(R.styleable.BadgeView_badgeBottom, 0);
        badgeLeft = array.getDimension(R.styleable.BadgeView_badgeLeft,  0);
        //关闭清空TypedArray
        array.recycle();

        // 初始化主体文字描述的画笔
        textPaint = new TextPaint();
        textPaint.setAntiAlias(true);
        textPaint.setColor(textColor);
        textPaint.setTextSize(textSize);
        textPaint.setTextAlign(Paint.Align.CENTER);

        contentPaint = new Paint();
        contentPaint.setAntiAlias(true);
    }

    public void setBadgeNum(int badgeNum) {
        this.badgeNum = badgeNum;
    }

    public void setShowNum(boolean isShow) {
        this.showNum = isShow;
    }

    public void setIconSrc(int res) {
        this.iconSrc = res;
    }

    public void setBadgeLocation(float bottom, float left) {
        this.badgeBottom = bottom;
        this.badgeLeft = left;
        hasBadgeBottomAttr = true;
        hasBadgeLeftAttr = true;
    }

    /**
     * 重新计算绘制这个View
     */
    public void redraw() {
        // 需要重新计算高宽,所以用这个
        requestLayout();
//        invalidate();
    }

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

        if (viewWidth != viewMinWidth || viewHeight != viewMinHeight) {
            canvas.save();
            // 若是设置的高宽大于所需要的高宽, 对画布进行操作
            float paddingLeft = viewPaddingLeft + (viewWidth - viewPaddingLeft - viewPaddingRight - viewMinWidth) / 2;
            float paddingTop = viewPaddingTop + (viewHeight -viewPaddingTop -viewPaddingBootom - viewMinHeight) / 2;
            // 移动布局, 改变原点
            canvas.translate(paddingLeft, paddingTop);
        }

        onDrawContent(canvas);

        if (viewWidth != viewMinWidth || viewHeight != viewMinHeight) {
            canvas.restore();
        }

    }

    /**
     * 绘制整个内容
     * @param canvas
     */
    private void onDrawContent(Canvas canvas) {
        if (iconSrc != 0) {
            // 画icon
            canvas.drawBitmap(iconBitmap, mainMarginHorizontal + (mainWidth - iconWidth) / 2, mainMarginTop + (mainHeight - iconHeight) / 2, contentPaint);
        } else {
            // 写text, 文字是居中的
            canvas.drawText(text, viewMinWidth / 2, viewMinHeight, textPaint);
        }

        if (badgeNum > 0) {
            canvas.save();
            // 移动布局, 改变原点
            canvas.translate(viewMinWidth - badgeWidth, 0);

            oDrawBadge(canvas);

            canvas.restore();
        }
    }

    private void oDrawBadge(Canvas canvas) {
        // 若有小红点有边缘线, 画边缘线
        if (badgeBorderWidth > 0) {
            contentPaint.setStyle(Paint.Style.STROKE);
            contentPaint.setColor(badgeBorderColor);
            contentPaint.setStrokeWidth(badgeBorderWidth);
            if (!showNum) {
                // 不显示数字
                canvas.drawCircle(badgeWidth / 2, badgeHeight - badgeRedSize / 2 - badgeBorderWidth, badgeRedSize / 2, contentPaint);
            } else if (badgeWidth == badgeHeight) {
                // 显示是字符串长度为1时, 为正圆
                canvas.drawCircle(badgeWidth / 2, badgeHeight / 2, badgeWidth / 2, contentPaint);
            } else {
                // 椭圆
                Path borderPath = new Path();
                borderPath.addArc(new RectF(0, 0, badgeHeight, badgeHeight), 90, 180);
                borderPath.lineTo(badgeWidth - badgeHeight / 2, 0);
                borderPath.addArc(new RectF(badgeWidth - badgeHeight, 0, badgeWidth, badgeHeight), 270, 180);
                borderPath.lineTo(badgeHeight / 2, badgeHeight);
                canvas.drawPath(borderPath, contentPaint);
            }
        }

        contentPaint.setColor(badgeBackgroundColor);
        contentPaint.setStyle(Paint.Style.FILL);
        if (showNum) {
            // 绘制红色背景图
            Path path = new Path();
            path.addArc(new RectF(badgeBorderWidth, badgeBorderWidth, badgeHeight - badgeBorderWidth, badgeHeight - badgeBorderWidth), 90, 180);
            path.lineTo(badgeWidth - badgeHeight / 2 + badgeBorderWidth, badgeBorderWidth);
            path.addArc(new RectF(badgeWidth - badgeHeight + badgeBorderWidth, badgeBorderWidth, badgeWidth - badgeBorderWidth, badgeHeight - badgeBorderWidth), 270, 180);
            path.lineTo(badgeHeight / 2 - badgeBorderWidth, badgeHeight - badgeBorderWidth);
            canvas.drawPath(path, contentPaint);
            // 写上数字
            canvas.drawText(showUneadText, badgeWidth / 2, badgeHeight - BADGE_TEXT_MARGIN_BOOTOM - badgeBorderWidth, badgeNumPaint);

        } else {
            // 画实心圆
            canvas.drawCircle(badgeRedSize / 2 + badgeBorderWidth, badgeHeight - badgeRedSize / 2 - badgeBorderWidth, badgeRedSize / 2, contentPaint);
        }
    }

    private void intParams() {
        // 初始化主体的一些数据
        if (iconSrc != 0) {
            mainHeight = iconHeight;
            mainWidth = iconWidth;
            if (iconBitmap == null) {
                Bitmap bitmap = BitmapFactory.decodeResource(getResources(), iconSrc);
                // 缩放图片
                int width = bitmap.getWidth();
                int height = bitmap.getHeight();
                // 保证icon的scaleType="fitCenter"
                // 获取图片的长边
                float length = width > height ? width : height;
                // 获取外框的最小边
                float size = iconWidth > iconHeight ? iconHeight : iconWidth;
                // 让图片按照长边进行缩放
                float scale = size / length;
                Matrix matrix = new Matrix();
                matrix.postScale(scale, scale);
                iconBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
            }
            // 因为icon是fitCenter, 所以有真实大小
            iconWidth = iconBitmap.getWidth();
            iconHeight = iconBitmap.getHeight();
        } else {
            // 字符描述文字的大小
            Rect descRect = new Rect();
            textPaint.getTextBounds(text, 0, text.length(), descRect);
            mainWidth = descRect.width();
            mainHeight = descRect.height();
        }

        // 初始化Badge的数据
        if (showNum) {
            showUneadText = getUnreadText(badgeNum);
            Rect badgeRect = new Rect();
            badgeNumPaint.getTextBounds(showUneadText, 0, showUneadText.length(), badgeRect);

            badgeNumWidth = badgeRect.width();

            if (showUneadText.length() == 1) {
                // 当长度为1的时候,显示正圆
                badgeWidth = badgeHeight;
            } else {
                badgeWidth = badgeNumWidth + BADGE_TEXT_MARGIN_LEFT + BADGE_TEXT_MARGIN_RIGHT + badgeBorderWidth * 2;
            }
        } else {
            badgeWidth = badgeRedSize + badgeBorderWidth * 2;
        }
        // badgeHeight在构造方法中初始化了, 全部使用数字模式的高度

        // Badge位置设置的范围做一个限制
        if (!hasBadgeLeftAttr || badgeLeft > mainWidth) {
            badgeLeft = getBadgeDefaultLocation();
        }
        if (!hasBadgeBottomAttr || badgeBottom > mainHeight) {
            badgeBottom = getBadgeDefaultLocation();
        }

        // 计算整体内容的大小
        mainMarginHorizontal = badgeWidth - badgeLeft;
        mainMarginTop = badgeHeight - badgeBottom;
        viewMinWidth = mainWidth + mainMarginHorizontal * 2;
        viewMinHeight = mainHeight + mainMarginTop;
    }

    /**
     * 获取默认的位置
     * @return
     */
    private float getBadgeDefaultLocation() {
        // 文字的时候默认往上些, 盖住文字了
        return iconSrc != 0 ? (showNum ? badgeHeight / 2 : badgeRedSize / 2 + badgeBorderWidth) : badgeRedSize / 2 + badgeBorderWidth - 3;
    }

    /**
     * 构造未读数显示的文本
     * 1) 未读数默认显示形式9/23/99+
     * 2) 有些设计要求未读前面加"+", (至少我们设计师这么设计) 显示成 +1/+34/+99, 取配置badgeNumPre
     * @param unread
     * @return
     */
    private String getUnreadText(int unread) {
        String text = String.valueOf(unread);
        if (TextUtils.isEmpty(badgeNumPre)) {
            if (unread > 99) {
                text = "99+";
            }
        } else {
            if (unread > 99) {
                text = badgeNumPre + "99";
            } else if (unread >= 0) {
                text = badgeNumPre + unread;
            }
        }
        return text;
    }

    private int dip2px(int dpValue) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics());
    }

    private int sp2px(int spValue) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, spValue, getResources().getDisplayMetrics());
    }


    /**
     * android-自定义View解决wrap_content无效的问题
     * see  https://my.oschina.net/ccqy66/blog/616662
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 计算高宽
        intParams();
        viewPaddingLeft = getPaddingLeft();
        viewPaddingTop = getPaddingTop();
        viewPaddingRight = getPaddingRight();
        viewPaddingBootom = getPaddingBottom();

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //Measure Width
        if (widthMode == MeasureSpec.EXACTLY) {
            // 设置的大小不能比内容还小
            viewWidth = widthSize < viewMinWidth ? viewMinWidth : widthSize;
        } else {
            viewWidth = viewMinWidth;
        }
        viewWidth += viewPaddingLeft + viewPaddingRight;

        //Measure Height
        if (heightMode == MeasureSpec.EXACTLY) {
            // 设置的大小不能比内容还小
            viewHeight = heightSize < viewMinHeight ? viewMinHeight : heightSize;
        } else {
            viewHeight = viewMinHeight;
        }
        if (viewHeight < viewMinHeight + VIEW_PADDING * 2) {
            viewHeight =  viewMinHeight + VIEW_PADDING * 2;
        }
        viewHeight += viewPaddingTop + viewPaddingBootom;

        //MUST CALL THIS
        setMeasuredDimension((int) Math.ceil(viewWidth), (int) Math.ceil(viewHeight));
    }
}

你可能感兴趣的:(Android自定义小红点BadgeView)