Android DrawPath to realize QQ drag and drop bubble

Source: Internet
Author: User
Tags cos sin

These two days learned to use path to draw Bezier curve related, and then do a self-similar QQ unread message can be dragged small bubbles, as follows:

The next step in the implementation of the entire process.

Basic principle

is actually using path to draw three points of two cubic Bezier curve to complete the enchanting curve. Then the corresponding circle is drawn according to the touch point, and the radius of the original fixed circle is changed according to the change of distance. Finally, the implementation of the return or burst after letting go.

Path Description:

As the name implies, is the meaning of a path, there are many methods in the path, the main use of this design related methods have

moveTo()Move path to a specified point
quadTo()draw two Bezier curves, receive two points, the first is the point of control radians, and the second is the end.
It lineTo() 's wired.
close()close the path path,
Reset reset() the relevant settings for path

    • Path starter Warm up:

      path.reset();path.moveTo(200, 200);//第一个坐标是对应的控制的坐标,第二个坐标是终点坐标path.quadTo(400, 250, 600, 200);canvas.drawPath(path, paint);canvas.translate(0, 200);//调用close,就会首尾闭合连接path.close();canvas.drawPath(path, paint);

      Remember not to new or yo in the OnDraw method Path Paint !

Specific implementation of the split:

In fact, the whole process is to draw a closed path path of two Bezier two curves, and then add two circles above.

    • Closed Path path implementation from the top left to draw two times Bezier curve to the lower left, the lower left line to the right, lower right point two times Bezier curve to the upper right point, finally closed!!

    • Determination of related coordinates
      This is one of the difficulties in the inside, because it involves a mathematical inside a sin,cos,tan and so on, I actually forget, and then the brain mended a bit, nonsense not much to say, direct!!

Why do you want to draw it yourself, because the painting you know, in the 360 rotation process, the angle of the system is two sets, if the use of a set to draw, it is now rotated in the process of the curve overlap in the case!

The problem has been thrown out, and then look directly at the code implementation!

Angle determination

According to the schematic diagram you can know, we can use the starting center coordinates and drag the center coordinates, according to the inverse tangent function to get the specific radian.

int dy = Math.abs(CIRCLEY - startY);int dx = Math.abs(CIRCLEX - startX); angle = Math.atan(dy * 1.0 / dx);

OK, the startx,y here is the coordinates of the moving process. Angle is the corresponding radian (angle) to be obtained.

Related path drawing

Already mentioned in the process of rotation there are two sets of coordinate system, at the beginning I also very tangled this coordinate system how to determine, behind and suddenly, in fact, the equivalent is 13 quadrant positive proportional growth, 24 quadrant, inverse proportion growth.

 flag = (startY - CIRCLEY  ) * (startX- CIRCLEX ) <= 0; //增加一个flag,用于判断使用哪种坐标体系。

The most important thing to do is to draw the relevant path path!

 Path.reset (); if (flag) {//First point Path.moveto ((float) (Circlex-math.sin (angle) * origin_radio), (float) (Circley-math.cos (angle) * Origin_radio)); Path.quadto ((float) ((StartX + circlex) * 0.5), (float) ((starty + circley) * 0.5), (float) (Startx-math.sin (angle) * DR Ag_radio), (float) (Starty-math.cos (angle) * drag_radio));p Ath.lineto ((float) (StartX + math.sin (angle) * drag_radio), ( float) (Starty + math.cos (angle) * drag_radio));p ath.quadto ((float) ((StartX + circlex) * 0.5), (float) ((Starty + Circley ) * 0.5), (float) (Circlex + math.sin (angle) * origin_radio), (float) (Circley + math.cos (angle) * origin_radio));p Ath.clo Se (); Canvas.drawpath (path, paint);  } else {//First point Path.moveto ((float) (Circlex-math.sin (angle) * origin_radio), (float) (Circley + math.cos (angle)     * Origin_radio)); Path.quadto ((float) ((StartX + circlex) * 0.5), (float) ((starty + circley) * 0.5), (float) (Startx-math.sin (angle) * DR Ag_radio), (float) (Starty + math.cos (angle) * Drag_radIO));     Path.lineto (float) (StartX + math.sin (angle) * drag_radio), (float) (Starty-math.cos (angle) * drag_radio)); Path.quadto ((float) ((StartX + circlex) * 0.5), (float) ((starty + circley) * 0.5), (float) (Circlex + math.sin (angle) * O     Rigin_radio), (float) (Circley-math.cos (angle) * origin_radio));     Path.close (); Canvas.drawpath (path, paint); }

The code here is to Java the relevant mathematical formula on the picture!

Here, in fact, the main work is almost finished!
Next, set the paint effect to fill, and then draw two more circles at a later

 paint.setStyle(Paint.Style.FILL) canvas.drawCircle(CIRCLEX, CIRCLEY, ORIGIN_RADIO, paint);//默认的 canvas.drawCircle(startX == 0 ? CIRCLEX : startX, startY == 0 ? CIRCLEY : startY, DRAG_RADIO, paint);//拖拽的

You can draw the desired effect!

We have to say onTouch the deal here!

  case MotionEvent.ACTION_DOWN://有事件先拦截再说!!            getParent().requestDisallowInterceptTouchEvent(true);            CurrentState = STATE_IDLE;            animSetXY.cancel();            startX = (int) ev.getX();            startY = (int) ev.getRawY();            break;

Handle the pit of event distribution!

Measurement and layout

This is basically passable, but our layout of what has not been dealt with, math_parent is absolutely impossible to use to the specific project!
When measuring, if the detection is not accurate mode, then manual to calculate the required width and height.

 @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    int modeWidth = MeasureSpec.getMode(widthMeasureSpec);    int modeHeight = MeasureSpec.getMode(heightMeasureSpec);    if (modeWidth == MeasureSpec.UNSPECIFIED || modeWidth == MeasureSpec.AT_MOST) {        widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_RADIO * 2, MeasureSpec.EXACTLY);    }    if (modeHeight == MeasureSpec.UNSPECIFIED || modeHeight == MeasureSpec.AT_MOST) {        heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_RADIO * 2, MeasureSpec.EXACTLY);    }    super.onMeasure(widthMeasureSpec, heightMeasureSpec);}

Then, when the layout changes, get the relevant coordinates and determine the initial center coordinates:

@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {    super.onSizeChanged(w, h, oldw, oldh);    CIRCLEX = (int) ((w) * 0.5 + 0.5);    CIRCLEY = (int) ((h) * 0.5 + 0.5);}

The manifest file can then be configured like this:

 <    android:id="@+id/dbv"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:layout_gravity="center"/>

After this, there will be a problem, that is wrap_content , the view can be drawn only the area of its own size, dragged and can't see! What about this pit, actually very simple, the parent layout plus android:clipChildren="false" the attributes!
This hole is solved!!

Determination of related states

We do not want it can be unlimited drag, is a drag the furthest distance, there is to let go after the return, burst. So corresponding, here are a few states to determine:

  private final static int STATE_IDLE = 1;//静止的状态    private final static int STATE_DRAG_NORMAL = 2;//正在拖拽的状态    private final static int STATE_DRAG_BREAK = 3;//断裂后的拖拽状态    private final static int STATE_UP_BREAK = 4;//放手后的爆裂的状态    private final static int STATE_UP_BACK = 5;//放手后的没有断裂的返回的状态    private final static int STATE_UP_DRAG_BREAK_BACK = 6;//拖拽断裂又返回的状态    private int CurrentState = STATE_IDLE;private int MIN_RADIO = (int) (ORIGIN_RADIO * 0.4);//最小半径    private int MAXDISTANCE = (int) (MIN_RADIO * 13);//最远的拖拽距离

Once you've identified this, you'll need to make a decision when you move:

  Case motionevent.action_move://When moving StartX = (int) ev.getx ();            Starty = (int) ev.gety ();            Updatepath ();            Invalidate ();    break;private void Updatepath () {int dy = Math.Abs (circley-starty);    int dx = Math.Abs (CIRCLEX-STARTX);    Double dis = math.sqrt (dy * dy + dx * dx); if (dis <= maxdistance) {//increment case, the original radius decreases if (CurrentState = = State_drag_break | |        CurrentState = = state_up_drag_break_back) {currentstate = State_up_drag_break_back;        } else {currentstate = State_drag_normal;        } Origin_radio = (int) (Default_radio-(dis/maxdistance) * (Default_radio-min_radio));        LOG.E (TAG, "Distance:" + (int) ((1-dis/maxdistance) * min_radio));    LOG.I (TAG, "Distance:" + origin_radio);    } else {currentstate = State_drag_break;    }//distance = dis;    Flag = (Starty-circley) * (Startx-circlex) <= 0;   LOG.I ("TAG", "Updatepath:" + flag); Angle = Math.atan (dy * 1.0/dx);} 

updatePath()Method has been seen before, this time is complete.
The thing to do here is to change the relevant state based on the distance dragged, and modify the radius of the original circle according to the percentage. There is the determination of the relevant radian before the introduction!

The last time you let go:

   case MotionEvent.ACTION_UP:            if (CurrentState == STATE_DRAG_NORMAL) {                CurrentState = STATE_UP_BACK;                valueX.setIntValues(startX, CIRCLEX);                valueY.setIntValues(startY, CIRCLEY);                animSetXY.start();            } else if (CurrentState == STATE_DRAG_BREAK) {                CurrentState = STATE_UP_BREAK;                invalidate();            } else {                CurrentState = STATE_UP_DRAG_BREAK_BACK;                valueX.setIntValues(startX, CIRCLEX);                valueY.setIntValues(startY, CIRCLEY);                animSetXY.start();            }            break;

Automatically return to use here ValueAnimator ,

  animsetxy = new Animatorset ();    Valuex = Valueanimator.ofint (StartX, Circlex);    Valuey = Valueanimator.ofint (Starty, Circley);    Animsetxy.playtogether (Valuex, Valuey);    Valuex.setduration (500);    Valuey.setduration (500);    Valuex.setinterpolator (New Overshootinterpolator ());    Valuey.setinterpolator (New Overshootinterpolator ()); Valuex.addupdatelistener (New Valueanimator.animatorupdatelistener () {@Override public void Onanimationupdat            E (valueanimator animation) {StartX = (int) animation.getanimatedvalue ();            LOG.E (TAG, "ONANIMATIONUPDATE-STARTX:" + StartX);        Invalidate ();    }    }); Valuey.addupdatelistener (New Valueanimator.animatorupdatelistener () {@Override public void Onanimationupdat            E (valueanimator animation) {starty = (int) animation.getanimatedvalue ();            LOG.E (TAG, "Onanimationupdate-starty:" + starty);        Invalidate (); }    });

Finally look at the complete onDraw Method!

 @Overrideprotected void OnDraw (canvas canvas) {switch (currentstate) {case state_idle://idle state, draw the default circle            if (showcircle) {canvas.drawcircle (Circlex, Circley, Origin_radio, paint);//default}        Break            Case STATE_UP_BACK://performs the returned animation case state_drag_normal://drag state to draw Bezier curves and two round path.reset (); if (flag) {//First point Path.moveto ((float) (Circlex-math.sin (angle) * origin_radio), (float) (                Circley-math.cos (angle) * origin_radio)); Path.quadto ((float) ((StartX + circlex) * 0.5), (float) ((starty + circley) * 0.5), (float) (Startx-math.sin (angle) * DR                Ag_radio), (float) (Starty-math.cos (angle) * drag_radio));                Path.lineto (float) (StartX + math.sin (angle) * drag_radio), (float) (Starty + math.cos (angle) * drag_radio)); Path.quadto ((float) ((StartX + circlex) * 0.5), (float) ((starty + circley) * 0.5), (float) (Circlex + math.sin (angle ) * ORIGin_radio), (float) (Circley + math.cos (angle) * origin_radio));                Path.close ();            Canvas.drawpath (path, paint); } else {//First point Path.moveto ((float) (Circlex-math.sin (angle) * origin_radio), (float) (CIR                Cley + math.cos (angle) * origin_radio)); Path.quadto ((float) ((StartX + circlex) * 0.5), (float) ((starty + circley) * 0.5), (float) (Startx-math.sin (angle) * DR                Ag_radio), (float) (Starty + math.cos (angle) * drag_radio));                Path.lineto (float) (StartX + math.sin (angle) * drag_radio), (float) (Starty-math.cos (angle) * drag_radio)); Path.quadto ((float) ((StartX + circlex) * 0.5), (float) ((starty + circley) * 0.5), (float) (Circlex + math.sin (angle                ) * Origin_radio), (float) (Circley-math.cos (angle) * origin_radio));                Path.close ();            Canvas.drawpath (path, paint); } if (showcircle) {canvas.drawcircle (Circlex, CIrcley, Origin_radio, paint);//default canvas.drawcircle (StartX = = 0?) Circlex:startx, Starty = = 0?        Circley:starty, Drag_radio, paint);//drag} break; Case state_drag_break://dragged to the upper limit, drawing the dragged Circle: Case State_up_drag_break_back:if (showcircle) {CA Nvas.drawcircle (StartX = = 0?) Circlex:startx, Starty = = 0?        Circley:starty, Drag_radio, paint);//drag} break;            Case state_up_break://painted a burst effect canvas.drawcircle (startX-25, startY-25, Circlepaint);            Canvas.drawcircle (StartX +, Starty +, ten, circlepaint);            Canvas.drawcircle (StartX, startY-25, ten, circlepaint);            Canvas.drawcircle (StartX, Starty, Circlepaint);            Canvas.drawcircle (startX-25, Starty, ten, circlepaint);    Break }}

Here, the finished product is out!!


1, determine the default circle coordinates;
2, according to the situation of move, real-time access to the latest coordinates, according to the moving distance (determine the angle), update the relevant state, draw the relevant path path. exceeds the upper limit and no longer draws the path path.
3, let go, according to the relevant state, or with path path to perform animation return, or do not take the path directly back, or directly burst!

Related source Please visit GitHub, like please powder a bar, have questions welcome message or issue.

Android DrawPath to realize QQ drag and drop bubble

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.