美文网首页
Android自定义View之仿QQ拖拽气泡效果

Android自定义View之仿QQ拖拽气泡效果

作者: loren325 | 来源:发表于2021-01-30 16:31 被阅读0次
    话不多说,先上效果图: aa.gif

    一、实现思路

    在列表中默认使用自定义的TextView控件来展示消息气泡,在自定义的TextView控件中重写onTouchEvent方法,然后在DOWN、MOVE、UP事件中分别处理拖拽效果。
    整个拖拽效果我们可以拆分成以下几步来实现:
    1.默认状态
    2.两气泡相连状态
    3.两气泡分离状态
    4.气泡消失状态

    二、功能实现

    默认状态:用来做一个状态的标识,无需特别处理。
    两气泡相连状态:绘制一个固定圆和一个移动圆,使用两条贝塞尔曲线来实现两气泡连接的曲线,两条贝塞尔曲线共用同一个控制点,然后根据MOVE事件中的坐标不断重绘移动圆。
    实现两气泡连接的效果,需要先计算出一些点的坐标,这也是整个拖拽气泡效果的核心部分,具体如下图:

    思路图.png
    如图,A点到B点是一条二阶贝塞尔曲线,C点到D点也是一条二阶贝塞尔曲线,它们共用同一个控制点,所以我们要计算出A点、B点、C点、D点以及控制点的坐标。
    首先来计算控制点的坐标,控制点的坐标和容易计算出,也就是固定圆的x坐标加上移动圆的x坐标,再除以2,固定圆的y坐标同理得出。
    int controlX = (int) ((mBubStillCenter.x + mBubMoveCenter.x) / 2);
    int controlY = (int) ((mBubStillCenter.y + mBubMoveCenter.y) / 2);
    

    根据图中所标注的信息得知,∠a=∠d,∠b=∠c,∠a=∠θ,由此可知,我们求出∠θ所在的直角三角形的sin和cos值,就可以计算出A点、B点、C点、D点的坐标。
    sin值可以通过移动圆的y坐标减去固定圆的y坐标,再除以两圆心的距离,也就是O1到O2的距离。
    cos值可以通过移动圆的x坐标减去固定圆的x坐标,再除以两圆心的距离。

    float sin = (mBubMoveCenter.y - mBubStillCenter.y) / mDist;
    float cos = (mBubMoveCenter.x - mBubStillCenter.x) / mDist;
    

    有了sin和cos值,对应的A点、B点、C点、D点的坐标就好计算了

    // A点
    float bubbleStillStartX = mBubStillCenter.x + mBubbleStillRadius * sin;
    float bubbleStillStartY = mBubStillCenter.y - mBubbleStillRadius * cos;
    // B点
    float bubbleMoveStartX = mBubMoveCenter.x + mBubbleMoveRadius * sin;
    float bubbleMoveStartY = mBubMoveCenter.y - mBubbleMoveRadius * cos;
    // C点
    float bubbleMoveEndX = mBubMoveCenter.x - mBubbleMoveRadius * sin;
    float bubbleMoveEndY = mBubMoveCenter.y + mBubbleMoveRadius * cos;
    // D点
    float bubbleStillEndX = mBubStillCenter.x - mBubbleStillRadius * sin;
    float bubbleStillEndY = mBubStillCenter.y + mBubbleStillRadius * cos;
    

    接下来就是把这些贝塞尔曲线和直线连起来,就实现了两气泡相连的效果。
    两气泡分离状态:当拖拽的移动圆超出固定圆一定范围时,就进入了两气泡分离状态,此时我们只需要绘制移动圆即可。当拖拽的移动圆回到固定圆一定范围时,此时会进入两气泡相连状态,并且需要实现一个气泡还原的效果。(这里会有个难点,就是移动圆我们可以在屏幕上任意拖动而不被遮挡,这里放到后面来实现。)

    public void move(float curX, float curY) {
        mBubMoveCenter.x = curX;
        mBubMoveCenter.y = curY;
        mDist = (float) Math.hypot(curX - mBubStillCenter.x, curY - mBubStillCenter.y);
        if(mBubbleState == BUBBLE_STATE_CONNECT){
            if(mDist < mMaxDist - MOVE_OFFSET){
                mBubbleStillRadius = mBubbleRadius - mDist / 10;
            }else {
                mBubbleState = BUBBLE_STATE_APART;
            }
        }
        invalidate();
    }
    

    mDist就是两圆心的距离。

    /**
     * 气泡还原动画
     */
    private void startBubbleRestAnim() {
        mBubbleStillRadius = mBubbleRadius;
        ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(), new PointF(mBubMoveCenter.x, mBubMoveCenter.y), new PointF(mBubStillCenter.x, mBubStillCenter.y));
        animator.setDuration(200);
        animator.setInterpolator(input -> {
            float factor = 0.4f;
            return (float) (Math.pow(2, -10 * factor) * Math.sin((input - factor / 4) * (2 * Math.PI) / factor) + 1);
        });
        animator.addUpdateListener(animation -> {
            mBubMoveCenter = (PointF) animation.getAnimatedValue();
            invalidate();
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mBubbleState = BUBBLE_STATE_DEFAULT;
                removeDragView();
                if(mDragListener != null){
                    mDragListener.onRestore();
                }
            }
        });
        animator.start();
    }
    

    分享一个可视化插值器的网站,其中内置了一些插值器公式,还可以查看动画演示效果。http://inloop.github.io/interpolator/

    气泡消失状态:当拖拽的移动圆超出一定范围时,并且松开了手指后,此时进入气泡消失状态,此时我们需要实现一个爆炸的动画。
    爆炸的动画通过绘制一组图片来实现

    if(mBubbleState == BUBBLE_STATE_DISMISS){
        if(mIsBurstAnimStart){
            mBurstRect.set((int)(mBubMoveCenter.x - mBubbleMoveRadius), (int)(mBubMoveCenter.y - mBubbleMoveRadius),
                    (int)(mBubMoveCenter.x + mBubbleMoveRadius), (int)(mBubMoveCenter.y + mBubbleMoveRadius));
            canvas.drawBitmap(mBurstBitmapArray[mCurDrawableIndex], null, mBurstRect, mBurstPaint);
        }
    }
    

    mCurDrawableIndex是图片的索引,是通过属性动画来改变

    /**
     * 气泡爆炸动画
     */
    private void startBubbleBurstAnim() {
        ValueAnimator animator = ValueAnimator.ofInt(0, mBurstDrawablesArray.length);
        animator.setInterpolator(new LinearInterpolator());
        animator.setDuration(1000);
        animator.addUpdateListener(animation -> {
            mCurDrawableIndex = (int) animator.getAnimatedValue();
            invalidate();
        });
        animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mIsBurstAnimStart = false;
                if(mDragListener != null){
                    mDragListener.onDismiss();
                }
            }
        });
        animator.start();
    }
    

    三、全屏拖拽效果实现

    首先在DOWN事件中获取当前触摸位置在全屏所在位置,然后将当前view缓存为bitmap,并把此bitmap添加到rootview中,拖动的时候直接绘制此bitmap。

    //获得当前View在屏幕上的位置
    int[] cLocation = new int[2];
    getLocationOnScreen(cLocation);
    
    if(rootView instanceof ViewGroup){
        mDragDotView = new DragDotView(getContext());
    
        //设置固定圆和移动圆的圆心坐标
        mDragDotView.setDragPoint(cLocation[0] + mWidth / 2, cLocation[1] + mHeight / 2, mRawX, mRawY);
    
        Bitmap bitmap = getBitmapFromView(this);
        if(bitmap != null){
            mDragDotView.setCacheBitmap(bitmap);
            ((ViewGroup) rootView).addView(mDragDotView);
            setVisibility(INVISIBLE);
        }
    }
    
    /**
     * 将当前view缓存为bitmap,拖动的时候直接绘制此bitmap
     * @param view
     * @return
     */
    public Bitmap getBitmapFromView(View view)
    {
        Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(bitmap);
        view.draw(canvas);
        return bitmap;
    }
    

    至此,整个消息气泡拖拽效果的核心部分就实现了,完整代码见https://github.com/loren325/CustomerView

    相关文章

      网友评论

          本文标题:Android自定义View之仿QQ拖拽气泡效果

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