Reproduced please indicate the source blog.csdn.net/qq_31715429… This article is written by Mr. Monkey Mushroom’s blog

The last section has a preliminary understanding of the Android Bezier curve, this section is a chestnut exercise, imitation QQ unread message bubble, is the most classic exercise bezier curve east, the effect is as follows

The idea is to draw two circles, with a sticky ball fixed to a point and a bubble ball changing coordinates as your finger slides. As the two circles get more and more apart, the radius of the sticky sphere gets smaller and smaller. When the spacing is less than a certain value, loosen the finger bubble ball will restore the original position; When the spacing exceeds a certain value, the adhesive ball disappears and the bubble ball continues to move with the finger. At this time, the finger is released and the bubble ball disappears

Create attrs. XML file, write custom attributes, create DragBubbleView inherit View, override constructor, get custom attribute values, initialize Paint, Path, etc., override onMeasure to calculate width and height

2. In onSizeChanged method determine the center coordinates of the glue ball and bubble ball, here we take half of the width and height:

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mBubbleCenterX = w / 2;
        mBubbleCenterY = h / 2;
        mCircleCenterX = mBubbleCenterX;
        mCircleCenterY = mBubbleCenterY;
    }Copy the code

3. After analysis, the bubble ball has the following states: default, drag, move and disappear. Let’s define it here for the convenience of analyzing different situations according to different states:

    /* The bubble state */
    private int mState;
    /* Default, cannot drag */
    private static final int STATE_DEFAULT = 0x00;
    / * drag * /
    private static final int STATE_DRAG = 0x01;
    Move / * * /
    private static final int STATE_MOVE = 0x02;
    / * * /
    private static final int STATE_DISMISS = 0x03;Copy the code

4. Rewrite the onTouchEvent method, where D represents the distance between the centers of two circles and maxD represents the maximum distance that can be dragged:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if(mState ! = STATE_DISMISS) { d = (float) Math.hypot(event.getX() - mBubbleCenterX, event.getY() - mBubbleCenterY);
                    if (d < mBubbleRadius + 48) {
                        // When the fingertip coordinates are inside the circle, it is considered draggable
                        // Bubbles are usually small, add 48 pixels for easier drag
                        mState = STATE_DRAG;
                    } else{ mState = STATE_DEFAULT; }}break;
            case MotionEvent.ACTION_MOVE:
                if(mState ! = STATE_DEFAULT) { mBubbleCenterX =event.getX();
                    mBubbleCenterY = event.getY();
                    // Calculate the distance between bubble center and sticky ball center
                    d = (float) Math.hypot(mBubbleCenterX - mCircleCenterX, mBubbleCenterY - mCircleCenterY);
                    //float d = (float) Math.sqrt(Math.pow(mBubbleCenterX - mCircleCenterX, 2) 
                    + Math.pow(mBubbleCenterY - mCircleCenterY, 2));
                    if (mState == STATE_DRAG) {// If you can drag
                        // The spacing is smaller than the maximum bonding distance
                        if (d < maxD - 48) {// Subtract 48 pixels to make the radius of the sticky ball disappear when it reaches a smaller value
                            mCircleRadius = mBubbleRadius - d / 5;// Make the radius of the sticky ball gradually smaller
                            mOnBubbleStateListener.onDrag();
                        } else {// The spacing is greater than the maximum bonding distance
                            mState = STATE_MOVE;// Change to move state
                            mOnBubbleStateListener.onMove();
                        }
                    }
                    invalidate();
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mState == STATE_DRAG) {// Release your finger while dragging, the bubble returns to its original position and vibrates
                    setBubbleRestoreAnim();
                } else if (mState == STATE_MOVE) {// Release your finger while you are moving and the bubble disappears
                    setBubbleDismissAnim();
                }
                break;
        }
        return true;
    }Copy the code

5, onDraw method in the circle, bezier curve, draw the number of messages text:

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);// Drag the bubble canvas.drawCircle(mBubbleCenterX, mBubbleCenterY, mBubbleRadius, mBubblePaint);

        if (mState == STATE_DRAG && d < maxD - 48) {// Draw a small circle of canvas.drawCircle(mCircleCenterX, mCircleCenterY, mCircleRadius, mBubblePaint);/ / computing quadratic bezier do need as the starting point and end point and control point coordinates calculateBezierCoordinate ();// Draw the second order Bezier path.reset(a);
            mBezierPath.moveTo(mCircleStartX, mCircleStartY);
            mBezierPath.quadTo(mControlX, mControlY, mBubbleEndX, mBubbleEndY);
            mBezierPath.lineTo(mBubbleStartX, mBubbleStartY);
            mBezierPath.quadTo(mControlX, mControlY, mCircleEndX, mCircleEndY);
            mBezierPath.close(a);
            canvas.drawPath(mBezierPath, mBubblePaint);} // Draw the number of messages text if (mState! = STATE_DISMISS && ! TextUtils.isEmpty(mText)) {
            mTextPaint.getTextBounds(mText, 0, mText.length(), mTextRect);
            canvas.drawText(mText, mBubbleCenterX - mTextRect.width(a) /2, mBubbleCenterY + mTextRect.height(a) /2, mTextPaint);}}Copy the code

Calculate the starting point, ending point and control point coordinates of the second-order Bessel curve. The order is moveTo A, quadTo B, lineTo C, quadTo D, close.

On the code

/** * Calculate the coordinates of the starting point, ending point and control point */
private void calculateBezierCoordinate() {// Calculate the coordinates of the control point, which is the midpoint of the line between the centers of two circles
    mControlX = (mBubbleCenterX + mCircleCenterX) / 2;
    mControlY = (mBubbleCenterY + mCircleCenterY) / 2;
    // Calculate the starting and ending points of two second-order Bezier curves
    float sin = (mBubbleCenterY - mCircleCenterY) / d;
    float cos = (mBubbleCenterX - mCircleCenterX) / d;
    mCircleStartX = mCircleCenterX - mCircleRadius * sin;
    mCircleStartY = mCircleCenterY + mCircleRadius * cos;
    mBubbleEndX = mBubbleCenterX - mBubbleRadius * sin;
    mBubbleEndY = mBubbleCenterY + mBubbleRadius * cos;
    mBubbleStartX = mBubbleCenterX + mBubbleRadius * sin;
    mBubbleStartY = mBubbleCenterY - mBubbleRadius * cos;
    mCircleEndX = mCircleCenterX + mCircleRadius * sin;
    mCircleEndY = mCircleCenterY - mCircleRadius * cos;
}Copy the code

6. Finally, the animation of bubble recovery and disappearance

    /** * Set the bubble recovery animation */
    private void setBubbleRestoreAnim() {
        ValueAnimator animX = ValueAnimator.ofFloat(mBubbleCenterX, mCircleCenterX);
        animX.setDuration(200);
        // Use OvershootInterpolator to interpolate the vibration effect
        animX.setInterpolator(new OvershootInterpolator(4));
        animX.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mBubbleCenterX = (float) animation.getAnimatedValue(); invalidate(); }}); animX.start(); ValueAnimator animY = ValueAnimator.ofFloat(mBubbleCenterY, mCircleCenterY); animY.setDuration(200);
        animY.setInterpolator(new OvershootInterpolator(4));
        animY.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mBubbleCenterY = (float) animation.getAnimatedValue(); invalidate(); }}); animY.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                // State is changed to default after animationmState = STATE_DEFAULT; mOnBubbleStateListener.onRestore(); }}); animY.start(); }/** * Set the bubble to disappear animation */
    private void setBubbleDismissAnim() {
        ValueAnimator anim = ValueAnimator.ofFloat(mBubbleRadius, 0);
        anim.setDuration(200);
        anim.setInterpolator(new AnticipateOvershootInterpolator(3));
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                mBubbleRadius = (float) animation.getAnimatedValue(); invalidate(); }}); anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mState = STATE_DISMISS;// The bubble disappearsmOnBubbleStateListener.onDismiss(); }}); anim.start(); }Copy the code

7, incidentally come to a bubble state listener, convenient external call to monitor its state:

    /** * Bubble state listener */
    public interface OnBubbleStateListener {
        /** * drag the bubble */
        void onDrag();

        /** * move bubble */
        void onMove();

        /** * Bubbles return to original position */
        void onRestore();

        /** * Bubbles disappear */
        void onDismiss();
    }

    /** * Sets the bubble state listener */
    public void setOnBubbleStateListener(OnBubbleStateListener onBubbleStateListener) {
        mOnBubbleStateListener = onBubbleStateListener;
    }Copy the code

8. Use this control in a layout file with custom properties:


       
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:monkey="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <com.monkey.dragpopview.DragBubbleView
        android:id="@+id/dragBubbleView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        monkey:text="99 +" />

</RelativeLayout>Copy the code

9. In MainActivity:

    DragBubbleView dragBubbleView = (DragBubbleView) findViewById(R.id.dragBubbleView);
    dragBubbleView.setOnBubbleStateListener(new DragBubbleView.OnBubbleStateListener() {
        @Override
        public void onDrag() {
            Log.e("- >"."Drag bubble");
        }

        @Override
        public void onMove() {
            Log.e("- >"."Moving bubble");
        }

        @Override
        public void onRestore() {
            Log.e("- >"."Bubble returns to original position.");
        }

        @Override
        public void onDismiss() {
            Log.e("- >"."Bubble gone"); }});Copy the code

Summary this practice not only custom View, but also includes the Bezier curve, the calculation of coordinates must be drawn, simple and intuitive. Finally attached source code address: github.com/MonkeyMushr…