轉載請注意: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
方法:
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()
接下來我們看一下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 * ( - getPointDistance(mRowBadgeCenter, mDragCenter) / mFinalDragDistance);
//設定拖拽效果的的判斷
if (mDraggable && mDragging) {
mDragQuadrant = getQuadrant(mDragCenter, mRowBadgeCenter);
showShadowImp(mShowShadow);
//根據拖拽距離設定狀态,來控制對BadgeView的繪制
if (mDragOutOfRange = startCircleRadius < DisplayUtils.dp2px(getContext(), f)) {
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());
}
}
}
drawBadge()
繪制BadgeView的方法。
private void drawBadge(Canvas canvas, PointF center, float radius) {
if (center.x == - && center.y == -) {
return;
}
mBadgeBackgroundPaint.setColor(mColorBackground);
mBadgeTextPaint.setColor(mColorBadgeText);
mBadgeTextPaint.setTextAlign(Paint.Align.CENTER);
//判斷是否需要在BadgeView之中設定文字,如果有文字設定文字沒有就是小圓點
if (mBadgeText.isEmpty() || mBadgeText.length() == ) {
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() / f + mBadgePadding);
mBadgeBackgroundRect.top = center.y - (mBadgeTextRect.height() / f + mBadgePadding * f);
mBadgeBackgroundRect.right = center.x + (mBadgeTextRect.width() / f + mBadgePadding);
mBadgeBackgroundRect.bottom = center.y + (mBadgeTextRect.height() / f + mBadgePadding * f);
canvas.drawRoundRect(mBadgeBackgroundRect,
DisplayUtils.dp2px(getContext(), ), DisplayUtils.dp2px(getContext(), ),
mBadgeBackgroundPaint);
}
//如果通過setBadgeText()方法設定了文字,就走開始繪制文字
if (!mBadgeText.isEmpty()) {
canvas.drawText(mBadgeText, center.x,
(mBadgeBackgroundRect.bottom + mBadgeBackgroundRect.top
- mBadgeTextFontMetrics.bottom - mBadgeTextFontMetrics.top) / f,
mBadgeTextPaint);
}
}
BadgeContainer類分析
我們可以比較直覺的看出來。這個類繼承自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 = ; i < getChildCount(); i++) {
View child = getChildAt(i);
child.layout(, , child.getMeasuredWidth(), child.getMeasuredHeight());
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
View targetView = null, badgeView = null;
//這個for為了将getChild擷取的子View進行判斷指派
for (int i = ; 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