转载请注意:http://blog.csdn.net/wjzj000/article/details/71597903
我和一帮应届生同学维护了一个公众号:IT面试填坑小分队。旨在帮助应届生从学生过度到开发者,并且每周树立学习目标,一同进步!
不知不觉又是半个月,愉愉快快,高高兴兴,舒舒服服…除了没学习啥事都干了….
这次记录一下一个开源框架的源码分析:https://github.com/qstumn/BadgeView
非常常见的一个效果:比如我们常见的消息提示:99+、小红点此类的效果。
首先我们从正常的使用,来具体的分析代码:
new QBadgeView(Context)
.bindTarget(目标View)
.setBadgeText("想要展示的文字")
.setBadgeTextColor(颜色)
.setBadgeGravity(Gravity.CENTER | Gravity.END)
.setBadgeBackgroundColor(背景颜色);
首先来说new QBadgeView()
并没有什么好说的,里边就是一些对画笔,线段,点的初始化信息。我们接下来重点看一下bindTarger
方法:
@Override
public Badge bindTarget(final View targetView) {
//很好理解,传入想要附着在哪个View的引用,如果为null,抛异常
if (targetView == null) {
throw new IllegalStateException("targetView can not be null");
}
//如果它的父View存在,移除父View上的这个QBadgeView
if (getParent() != null) {
((ViewGroup) getParent()).removeView(this);
}
//此处开始正式往View之中添加QBadgeView,也就是小红点等等效果
ViewParent targetParent = targetView.getParent();
if (targetParent != null && targetParent instanceof ViewGroup) {
//首先进行第一步判断,这个getParent()获取的对象是不是BadgeContainer类型(这是自定的类型,后文会对它进行分析。如果感觉不清楚这个类的源码怎么写就浑身难受的话,可以先往下拉,提前看看这个类),如果是这种类型,那么就直接进行addView
mTargetView = targetView;
if (targetParent instanceof BadgeContainer) {
((BadgeContainer) targetParent).addView(this);
} else {
//else之中我们可以看出来,这里我们可以看出使用BadgeContainer将targetView以及badgeView添加进去
ViewGroup targetContainer = (ViewGroup) targetParent;
int index = targetContainer.indexOfChild(targetView);
ViewGroup.LayoutParams targetParams = targetView.getLayoutParams();
targetContainer.removeView(targetView);
final BadgeContainer badgeContainer = new BadgeContainer(getContext());
badgeContainer.setId(targetView.getId());
targetContainer.addView(badgeContainer, index, targetParams);
badgeContainer.addView(targetView);
badgeContainer.addView(this);
}
} else {
throw new IllegalStateException("targetView must have round_stroke_black parent");
}
return this;
}
接下来我们看一下onDraw()方法中的具体实现,作为View中核心的部分,它主要负责对是否需要拖拽操作进行判断BadgeView的清楚效果。
@Override
protected void onDraw(Canvas canvas) {
//动画相关
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.draw(canvas);
return;
}
if (mBadgeText != null) {
//实现阴影效果的方法
showShadowImp(mShowShadow);
float badgeRadius = getBadgeCircleRadius();
float startCircleRadius = mDefalutRadius * (1 - getPointDistance(mRowBadgeCenter, mDragCenter) / mFinalDragDistance);
//设置拖拽效果的的判断
if (mDraggable && mDragging) {
mDragQuadrant = getQuadrant(mDragCenter, mRowBadgeCenter);
showShadowImp(mShowShadow);
//根据拖拽距离设置状态,来控制对BadgeView的绘制
if (mDragOutOfRange = startCircleRadius < DisplayUtils.dp2px(getContext(), 1.5f)) {
updataListener(OnDragStateChangedListener.STATE_DRAGGING_OUT_OF_RANGE);
//画BadgeView操作
drawBadge(canvas, mDragCenter, badgeRadius);
} else {
updataListener(OnDragStateChangedListener.STATE_DRAGGING);
drawDragging(canvas, startCircleRadius, badgeRadius);
drawBadge(canvas, mDragCenter, badgeRadius);
}
} else {
findBadgeCenter();
drawBadge(canvas, mBadgeCenter, getBadgeCircleRadius());
}
}
}
绘制BadgeView的方法。
private void drawBadge(Canvas canvas, PointF center, float radius) {
if (center.x == -1000 && center.y == -1000) {
return;
}
mBadgeBackgroundPaint.setColor(mColorBackground);
mBadgeTextPaint.setColor(mColorBadgeText);
mBadgeTextPaint.setTextAlign(Paint.Align.CENTER);
//判断是否需要在BadgeView之中设置文字,如果有文字设置文字没有就是小圆点
if (mBadgeText.isEmpty() || mBadgeText.length() == 1) {
mBadgeBackgroundRect.left = center.x - radius;
mBadgeBackgroundRect.top = center.y - radius;
mBadgeBackgroundRect.right = center.x + radius;
mBadgeBackgroundRect.bottom = center.y + radius;
canvas.drawCircle(center.x, center.y, radius, mBadgeBackgroundPaint);
} else {
//没有文字,那么就画小圆点
mBadgeBackgroundRect.left = center.x - (mBadgeTextRect.width() / 2f + mBadgePadding);
mBadgeBackgroundRect.top = center.y - (mBadgeTextRect.height() / 2f + mBadgePadding * 0.5f);
mBadgeBackgroundRect.right = center.x + (mBadgeTextRect.width() / 2f + mBadgePadding);
mBadgeBackgroundRect.bottom = center.y + (mBadgeTextRect.height() / 2f + mBadgePadding * 0.5f);
canvas.drawRoundRect(mBadgeBackgroundRect,
DisplayUtils.dp2px(getContext(), 100), DisplayUtils.dp2px(getContext(), 100),
mBadgeBackgroundPaint);
}
//如果通过setBadgeText()方法设置了文字,就走开始绘制文字
if (!mBadgeText.isEmpty()) {
canvas.drawText(mBadgeText, center.x,
(mBadgeBackgroundRect.bottom + mBadgeBackgroundRect.top
- mBadgeTextFontMetrics.bottom - mBadgeTextFontMetrics.top) / 2f,
mBadgeTextPaint);
}
}
我们可以比较直观的看出来。这个类继承自ViewGroup,并且这里的onLayout方法并没有多么复杂的处理,仅仅是简单的进行了最基本的布局。
让我们目光集结到onMeasure方法之中:View targetView = null, badgeView = null;
第一行代码就充满了有意思的地方。注意看变量名:targetView,以及badgeView。我们在上文中知道targetView是我们需要附着上的View,而它竟然出现在了BadgeContainer这个类之中!也就是说targetView必将被加入到BadgeContainer这个ViewGroup之中,让我们拭目以待。
以下源码分析直接以注释的方式写在源代码之中。
private class BadgeContainer extends ViewGroup {
public BadgeContainer(Context context) {
super(context);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
View targetView = null, badgeView = null;
//这个for为了将getChild获取的子View进行判断赋值
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
/**
* 如果类型不为QBadgeView那么就赋值给targetView
* 否则就赋值给badgeView,这就说明了我们上边推测的那个问题:
* BadgeContainer这个ViewGroup必将把附着的targetView和作为小红点badgeView包含在内
*/
if (!(child instanceof QBadgeView)) {
targetView = child;
} else {
badgeView = child;
}
}
if (targetView == null) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
//此时进行正常的measure处理
targetView.measure(widthMeasureSpec, heightMeasureSpec);
if (badgeView != null) {
badgeView.measure(MeasureSpec.makeMeasureSpec(targetView.getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(targetView.getMeasuredHeight(), MeasureSpec.EXACTLY));
}
setMeasuredDimension(targetView.getMeasuredWidth(), targetView.getMeasuredHeight());
}
}
}
OK,差不多分析到这,核心的东西已经差不多了。在结尾之处在梳理一遍这个过程,我们通过bindTarget()方法设置这个BadgeView,也就是小圆点效果依附与哪个View,然后通过一系列的setXXX方法进行设置相关的属性。
当然,我们这样说很简单,具体的流程还是大家自己走一遍,理解理解才是真正的极好的!
希望各位看官可以star我的GitHub,三叩九拜,满地打滚求star:
https://github.com/zhiaixinyang/PersonalCollect
https://github.com/zhiaixinyang/MyFirstApp