美文网首页
贝塞尔曲线之聊天未读数气泡-进化

贝塞尔曲线之聊天未读数气泡-进化

作者: CrazyCarrot | 来源:发表于2018-02-09 11:20 被阅读0次

    之前写了一个基础思路,实现了基础的功能,现在就需要让他可以露脸,提升颜值,提升作用,基础效果回顾:

    base.gif
    进化~
    evolve.gif
    基本效果都实现了~ 代码 略有粗糙~ 进化版在基础上优化了
    • 气泡的显示效果
    • 动态设置属性,并增加了几个属性
    • 添加到依附控件以及添加到屏幕,可以在整个屏幕内拖动

    首先看怎么实现文字多的时候使用椭圆背景,

        /**
         * 测量文本占用大小
         */
        private void measureText() {
            mBadgeTextRect.left = 0;
            mBadgeTextRect.top = 0;
            if (TextUtils.isEmpty(mTextStr)) {
                mBadgeTextRect.right = 0;
                mBadgeTextRect.bottom = 0;
            } else {
                mBadgeTextPaint.setTextSize(mTextSize);
                mBadgeTextRect.right = mBadgeTextPaint.measureText(mTextStr);
                mBadgeTextFontMetrics = mBadgeTextPaint.getFontMetrics();
                mBadgeTextRect.bottom = mBadgeTextFontMetrics.descent - mBadgeTextFontMetrics.ascent;
            }
        }
    
        public void DrawBubMoveable(Canvas canvas, PointF center, float radius) {
            if (mTextStr.isEmpty() || mTextStr.length() == 1) {
                //数字为一位的时候
                mBadgeBackgroundRect.left = center.x - (int) radius;
                mBadgeBackgroundRect.top = center.y - (int) radius;
                mBadgeBackgroundRect.right = center.x + (int) radius;
                mBadgeBackgroundRect.bottom = center.y + (int) radius;
                canvas.drawCircle(center.x, center.y, radius, mBubblePaint);
            } 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);
    
                radius = mBadgeBackgroundRect.height() / 2f;
                mBubMoveableRadius = radius;
                canvas.drawRoundRect(mBadgeBackgroundRect, radius, radius, mBubblePaint);
            }
    
    
            if (!mTextStr.isEmpty()) {
                canvas.drawText(mTextStr, center.x,
                        (mBadgeBackgroundRect.bottom + mBadgeBackgroundRect.top
                                - mBadgeTextFontMetrics.bottom - mBadgeTextFontMetrics.top) / 2f,
                        mBadgeTextPaint);
                //基线中点
                /*canvas.drawCircle(center.x,
                        (mBadgeBackgroundRect.bottom + mBadgeBackgroundRect.top) / 2 + (
                                mBadgeTextFontMetrics.bottom - mBadgeTextFontMetrics.top) / 2f - mBadgeTextFontMetrics.bottom, 10.0f,
                        mBadgeTextPaint);*/
            }
        }
    

    首先需要计算文本大小,知道文本的左上右下角,Paint.FontMetrics 可以获得文字的 top,ascent,desent, bottom, leading这几个属性

    text.png

    baseline以下是正值,以上是负值 descent - ascent 就是文字的高度了,基线的计算
    baseline = center +(FontMetrics.bottom - FontMetrics.top)/2 - FontMetrics.bottom

    更多相关知识移步HenCoder Android 开发进阶:自定义 View 1-3 drawText() 文字的绘制

    这里在文字长度大于2个的时候,计算一块区域,画椭圆就好~ 在计算椭圆半径的时候把mBubMoveableRadius,动圆的半径也赋值了,因为在画连接线的时候使用到了半径,值如果太大会超出气泡背景。其余页面优化就是在设置值后的,重新赋值,或者重新计算位置的逻辑。

                    new DragBubbleView(this)
                    .bindTarget(btn)
                    .setBadgeNumber(999)//设置数字
                    .setBadgeText("一二一")//设置文字
                    .setBadgeTextColor(getResources().getColor(R.color.colorAccent))//文字颜色
                    .setBadgeBackgroundColor(getResources().getColor(R.color.colorAccent))//背景颜色
                    .setBadgeTextSize(12, true)//设置文字大小
                    .setBadgePadding(5,true)//设置文字Padding
                    .setBadgeGravity(Gravity.BOTTOM|Gravity.START)//设置Gravity
                    .setGravityOffset(10,10,true)//设置偏移
                    .setBadgeBackgroundSize(12)//设置气泡半径  - - 别写太大,会有意想不到的效果
                    .setExactMode(true)//设置是否是精确值
                    .setOnDragStateChangedListener(new Badge.OnDragStateChangedListener() {
                        @Override
                        public void onDragStateChanged(int dragState, Badge badge, View targetView) {
                            switch (dragState) {
                                case BUBBLE_STATE_DISMISS:
                                    badge.setBadgeNumber(badge.getBadgeNumber() + 1);
                                    break;
                            }
                        }
                    });//设置监听才可以拖拽
    

    嗯~ 属性设置最后就成了这样
    在列表中使用的时候,为了防止复用导致的 重复显示,也可以使用xml初始化,然后对VIew进行显示隐藏,有人会问new出来的不能显示隐藏吗,亲测不行= =,原因还在寻找,如果有人遇到过同样的问题,还请指点一二~

        <dragbubbleevolve.beyond.com.dragbubbleevolve.DragBubbleView
            android:id="@+id/drag"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            />
    
        <!--
        app:bubble_color="#ff0000"
        app:bubble_radius="12dp"
        app:bubble_text="92"
        app:bubble_textColor="#ffffff"
        app:bubble_textSize="12sp"
        -->
    
    
                    DragBubbleView dragBubbleView = helper.getView(R.id.drag);
                    if (item) {
                        dragBubbleView.setVisibility(View.VISIBLE);
                        dragBubbleView
                                .bindTarget(textView)
                                .setBadgeNumber(100+helper.getLayoutPosition())//设置数字
                                .setBadgeTextSize(10, true)//设置文字大小
                                .setBadgeBackgroundSize(10)
                                .setExactMode(true)//设置是否是精确值
                                .setOnDragStateChangedListener(new Badge.OnDragStateChangedListener() {
                                    @Override
                                    public void onDragStateChanged(int dragState, Badge badge, View targetView) {
                                        switch (dragState) {
                                            case BUBBLE_STATE_DISMISS:
                                                getData().set(helper.getLayoutPosition(),false);
                                                break;
                                        }
                                    }
                                });
                    } else {
                        dragBubbleView.setVisibility(View.GONE);
                    }
    

    属性部分源码(链接在最下面)体现了,注意重置各个属性值就行,得动态属性嘛~ 剩下的就是怎么添加在控件上,怎么将气泡移出本身的范围~ 这应该是这个自定义View的难点了吧~

        @Override
        public Badge bindTarget(final View targetView) {
            if (targetView == null) {
                throw new IllegalStateException("targetView can not be null");
            }
            if (getParent() != null) {
                ((ViewGroup) getParent()).removeView(this);
            }
    
            ViewParent targetParent = targetView.getParent();
            if (targetParent != null && targetParent instanceof ViewGroup) {
                mTargetView = targetView;
                if (targetParent instanceof BadgeContainer) {
                    ((BadgeContainer) targetParent).addView(this);
                } else {
                    ViewGroup targetContainer = (ViewGroup) targetParent;
                    int index = targetContainer.indexOfChild(targetView);
                    ViewGroup.LayoutParams targetParams = targetView.getLayoutParams();
                    targetContainer.removeView(targetView);
                    final BadgeContainer badgeContainer = new BadgeContainer(getContext());
                    if (targetContainer instanceof RelativeLayout) {
                        badgeContainer.setId(targetView.getId());
                    }
                    targetContainer.addView(badgeContainer, index, targetParams);
                    badgeContainer.addView(targetView);
                    badgeContainer.addView(this);
                }
            } else {
                throw new IllegalStateException("targetView must have a parent");
            }
            return this;
        }
    

    先看这段,我相信很多类型的控件这段代码是经常见到的,首先BadgeContainer是一个自定义ViewGroup,他会添加把依附控件和自定义View添加到自身,并通过子View计算所占大小,并通过计算除的宽高来测量我们的自定义View。

            @Override
            protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
                View targetView = null, badgeView = null;
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    if (!(child instanceof DragBubbleView)) {
                        //拿到依附控件
                        targetView = child;
                    } else {
                        badgeView = child;
                    }
                }
                if (targetView == null) {
                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                } else {
                    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());
                }
            }
    

    然后拿到依附控件的父控件,把依附控件从父控件中删除,并把BadgeContainer 添加到父控件中,并把依附控件和自定义View添加到BadgeContainer 中。这样就把我们的自定义View添加在了控件上,但是,现在我们自定义VIew的操作范围,只是依附控件的大小,除非依附控件是全屏的,否者还是没有什么效果,所以我们就要把我们的自定义View再添加到更外层的父控件。
    不知道大家认不认识android.R.id.content,这是DecorView下一个FrameLayout的id,我这里直接添加在了id为android.R.id.content的view上。我正在写关于View绘制流程的文章,基本都是源码,所以还得多复查几遍。

        private void findActivityRoot(View view) {
            if (view.getParent() != null) {
                findActivityRoot((View) view.getParent());
            } else if (view instanceof FrameLayout && view.getId()==android.R.id.content) {
                mActivityRoot = (ViewGroup) view;
            }
        }
    

    然后在手势滑动的时候判断是添加在控件上还是添加在外层View上

        protected void screenFromWindow(boolean screen) {
            if (getParent() != null) {
                ((ViewGroup) getParent()).removeView(this);
            }
    
            if (screen) {
                //添加到最外层父类
                mActivityRoot.addView(this, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
                        FrameLayout.LayoutParams.MATCH_PARENT));
            } else {
                //添加控件上
                bindTarget(mTargetView);
            }
        }
    

    我在手指落下,可以构成连接状态的时候,将自定义View添加在了外层View上,在回弹动画,和爆炸动画结束后,重新添加在了依附控件上,如果不重新添加在依附控件上,会发现,屏幕所有 的点击事件都被 我们的自定义View拦截进行处理~
    这是在处理我们自定义View展示大小是应该注意的,还应该注意,手势处理时使用getRawY用的绝对坐标,之前基础中用的都是相对坐标,计算动圆圆心的时机,重置那些属性以及时机,还有就是很多处理逻辑。。。

    最后感谢进化内容参考的项目

    BadgeView

    没有这个项目,可能我还得好久才能写完进化部分的内容,特别感谢~
    写这个项目从最初的画圆,学习基线,写字,到现在的应该说可以在项目中使用(暂时在本机上没有什么明显的bug,性能方面还不太清楚),途中遇到了一位书友~ 一起解决讨论了很多问题,受益很大

    这篇文章是边学习边写Demo,然后写的文章,可想而知我还是个很小的菜鸟,如果其中有错误还请指出,我会尽快修改文章,并改正自己的理解,谢谢。

    最后附上源码

    我朋友和我说,在没看源码之前,根本想不到这种思路,但是看了源码时候,感觉又都会~ 个人理解,其实我们现在都只是一个学习积累的过程,这个效果用到了很多知识点,比如 path,贝塞尔曲线,手势,动画什么的,分成知识点去学习它,然后去组合它们,感觉也是很收益。

    你们的点赞,是我们学习分享的最大动力~ 每个人都一样,更何况我们这些菜鸟呢?

    相关文章

      网友评论

          本文标题:贝塞尔曲线之聊天未读数气泡-进化

          本文链接:https://www.haomeiwen.com/subject/cixuzxtx.html