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…