美文网首页
Android--仿QQ气泡

Android--仿QQ气泡

作者: aruba | 来源:发表于2020-02-03 15:25 被阅读0次
    BubbleView.gif
    主要利用三角函数和贝塞尔曲线实现粘连效果,角度和坐标对应关系如下
    QQ气泡.png
    /**
     * qq气泡
     */
    public class BubbleView extends View {
        //原始气泡半径
        private int radius;
        //气泡颜色
        private int bubbleColor;
        private Paint bubblePaint = new Paint();
        //数字
        private String textNumber;
        //字体颜色
        private int textColor;
        //字体大小
        private int textSize;
        private Paint textPaint = new Paint();
        //气泡初始坐标
        private PointF bubblePoint = new PointF();
        //气泡移动坐标
        private PointF bubbleMovePoint = new PointF();
        //移动气泡和初始点的距离
        private int dst;
        //移动气泡最大距离
        private int maxDst;
        //气泡状态
        private int status;
        //静止状态
        private final static int status_bubble_default = 0;
        //连接状态
        private final static int status_bubble_connect = 1;
        //断开状态
        private final static int status_bubble_disconnect = 2;
        //消失状态
        private final static int status_bubble_dismiss = 3;
        //数字和气泡的间距
        private float padding;
        //触摸偏移
        private int offset = 100;
    
        public BubbleView(Context context) {
            this(context, null);
        }
    
        public BubbleView(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public BubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            init(attrs);
        }
    
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        public BubbleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
            init(attrs);
        }
    
        private void init(AttributeSet attrs) {
            TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.BubbleView);
    
            textNumber = typedArray.getString(R.styleable.BubbleView_textNumber);
            if (textNumber == null) textNumber = "0";
    
            bubbleColor = typedArray.getColor(R.styleable.BubbleView_bubbleColor, Color.RED);
            textColor = typedArray.getColor(R.styleable.BubbleView_textColor, Color.WHITE);
            padding = typedArray.getDimensionPixelSize(R.styleable.BubbleView_padding, 15);
    
            int textSize = typedArray.getDimensionPixelSize(R.styleable.BubbleView_textSize, dpToPx(14));
            setTextSize(textSize);
    
            bubblePaint.setAntiAlias(true);
            bubblePaint.setColor(bubbleColor);
    
            textPaint.setAntiAlias(true);
            textPaint.setColor(textColor);
        }
    
        public void initView(int x, int y) {
            bubblePoint.x = bubbleMovePoint.x = x;
            bubblePoint.y = bubbleMovePoint.y = y;
    
            status = status_bubble_default;
            invalidate();
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            if (status != status_bubble_dismiss) {
                if (status == status_bubble_connect) {
                    //画原始气泡,随距离变大,半径不断变小
                    int initRadius = (int) (radius - dst / 8f);
                    canvas.drawCircle(bubblePoint.x, bubblePoint.y, initRadius, bubblePaint);
                    //画两条贝塞尔曲线
                    int anchorX, anchorY, pathAX, pathAY, pathBX, pathBY, pathCX, pathCY, pathDX, pathDY;
                    float sin = (bubbleMovePoint.y - bubblePoint.y) / (float) dst;
                    float cos = (bubbleMovePoint.x - bubblePoint.x) / (float) dst;
                    anchorX = (int) (bubblePoint.x + (bubbleMovePoint.x - bubblePoint.x) / 2);
                    anchorY = (int) (bubblePoint.y + (bubbleMovePoint.y - bubblePoint.y) / 2);
                    pathAX = (int) (bubblePoint.x - initRadius * sin);
                    pathAY = (int) (bubblePoint.y + initRadius * cos);
                    pathBX = (int) (bubbleMovePoint.x - (radius - 5) * sin);
                    pathBY = (int) (bubbleMovePoint.y + (radius - 5) * cos);
                    pathCX = (int) (bubbleMovePoint.x + (radius - 5) * sin);
                    pathCY = (int) (bubbleMovePoint.y - (radius - 5) * cos);
                    pathDX = (int) (bubblePoint.x + initRadius * sin);
                    pathDY = (int) (bubblePoint.y - initRadius * cos);
    
                    Path path = new Path();
                    path.moveTo(pathAX, pathAY);
                    path.quadTo(anchorX, anchorY, pathBX, pathBY);
    
                    path.lineTo(pathCX, pathCY);
                    path.quadTo(anchorX, anchorY, pathDX, pathDY);
                    path.close();
                    canvas.drawPath(path, bubblePaint);
                }
    
                //画文字
                Rect textRect = new Rect();
                textPaint.getTextBounds(textNumber, 0, textNumber.length(), textRect);
                Paint.FontMetrics m = new Paint.FontMetrics();
                textPaint.getFontMetrics(m);
                int baseLine = (int) (-(m.top + m.bottom) / 2);
    
                //画气泡
                RectF rectF = new RectF(textRect);
                if (textRect.height() > textRect.width()) {
                    rectF.left -= (textRect.height() - textRect.width()) / 2;
                    rectF.right += (textRect.height() - textRect.width()) / 2;
                }
                rectF.left -= padding;
                rectF.top -= padding;
                rectF.right += padding;
                rectF.bottom += padding;
                rectF.offset(bubbleMovePoint.x - textRect.width() / 2, bubbleMovePoint.y + textRect.height() / 2);
    
                radius = (int) (rectF.height() / 2);
                maxDst = radius * 8;
                canvas.drawRoundRect(rectF, radius, radius, bubblePaint);
    
                canvas.drawText(textNumber, bubbleMovePoint.x - textRect.width() / 2, bubbleMovePoint.y + baseLine, textPaint);
            }
        }
    
        /**
         * 设置文字字体大小
         *
         * @param textSize
         */
        public void setTextSize(int textSize) {
            this.textSize = textSize;
            textPaint.setTextSize(textSize);
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN: {
                    if (status != status_bubble_dismiss) {
                        dst = (int) Math.hypot(event.getX() - bubblePoint.x,
                                event.getY() - bubblePoint.y);
    
                        if (dst < offset) {//距离在点击触摸范围内,改变成连接状态
                            status = status_bubble_connect;
                            bubbleMovePoint.x = (int) event.getX();
                            bubbleMovePoint.y = (int) event.getY();
                            invalidate();
                        }
                    }
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    if (status != status_bubble_dismiss && status != status_bubble_default) {
                        dst = (int) Math.hypot(event.getX() - bubblePoint.x,
                                event.getY() - bubblePoint.y);
    
                        if (dst > maxDst - offset) {//距离大于最大距离,变为断开状态
                            status = status_bubble_disconnect;
                        }
    
                        bubbleMovePoint.x = (int) event.getX();
                        bubbleMovePoint.y = (int) event.getY();
                        invalidate();
                    }
                    break;
                }
                case MotionEvent.ACTION_UP: {
                    if (status != status_bubble_dismiss && status != status_bubble_default) {
    
                        if (status == status_bubble_disconnect && dst > radius * 2) {//断开状态,并距离大于2倍的半径,消失
                            status = status_bubble_dismiss;
                        } else {//还原为默认状态
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                                startResetAnim();
                            } else {
                                bubbleMovePoint.x = bubblePoint.x;
                                bubbleMovePoint.y = bubblePoint.y;
                                status = status_bubble_default;
                            }
                        }
    
                        invalidate();
                    }
                    break;
                }
            }
            return true;
        }
    
        private ValueAnimator resetValueAnimator;
    
        @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
        private void startResetAnim() {
            resetValueAnimator = ValueAnimator.ofObject(new PointFEvaluator(), new PointF(bubbleMovePoint.x, bubbleMovePoint.y),
                    new PointF(bubblePoint.x, bubblePoint.y));
            resetValueAnimator.setDuration(200);
            resetValueAnimator.setInterpolator(new OvershootInterpolator());
            resetValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    bubbleMovePoint = (PointF) animation.getAnimatedValue();
                    postInvalidate();
                }
            });
            resetValueAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    bubbleMovePoint.x = bubblePoint.x;
                    bubbleMovePoint.y = bubblePoint.y;
                    status = status_bubble_default;
                }
            });
    
            resetValueAnimator.start();
        }
    
        public int dpToPx(int size) {
            final float scale = getContext().getResources().getDisplayMetrics().density;
            return (int) (size * scale + 0.5f);
        }
    }
    
    项目地址:https://gitee.com/aruba/BubbleView.git

    相关文章

      网友评论

          本文标题:Android--仿QQ气泡

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