android 自定义小红点,Android自定义小红点BadgeView

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

效果就是这样....

2cc34a055bfe

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

}

}