天天看點

Android從零開搞系列:自定義View(13)新消息小圓點效果寫在前面開始尾聲

轉載請注意:http://blog.csdn.net/wjzj000/article/details/71597903

我和一幫應屆生同學維護了一個公衆号:IT面試填坑小分隊。旨在幫助應屆生從學生過度到開發者,并且每周樹立學習目标,一同進步!

Android從零開搞系列:自定義View(13)新消息小圓點效果寫在前面開始尾聲

寫在前面

不知不覺又是半個月,愉愉快快,高高興興,舒舒服服…除了沒學習啥事都幹了….

這次記錄一下一個開源架構的源碼分析: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