Android从零开搞系列:自定义View(13)新消息小圆点效果

转载请注意: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 * (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());
            }
        }
    }

drawBadge()

绘制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);
        }
    }

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 = 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

你可能感兴趣的:(Android,自定义View,从零开搞系列)