[IOS effect set] achieves QQ's elimination of red dots (one-click return)
The sticky little red dots on QQ are fun and interesting, so I want to implement them myself. I see that there are fewer iOS implementations and more Android ones, so I will use iOS to implement this ~
:
Debugging diagram:
In fact, in terms of implementation, I first realized the effect of the second figure.
Implementation
1. Understand the principle and how to draw the "adhesive" shape (that is, to draw two circles and two besell curves ).
2. Create a New UIView (AZMetaBallCanvas), which is used as a separate canvas to draw the "adhesive" shape, implement algorithms using programs, and draw them out.
3. Add a canvas (AZMetaBallCanvas)attach:(UIView *)
Method, and add a gesture listener, re-painting, so that any view can be "pasted" on the canvas.
4. Determine whether to disconnect the animation based on the distance of the heartbeat link. When the user's finger leaves, determine whether the animation is an explosive animation or a rebound animation based on the distance.
Detailed Process
First, you must understand what the shape of the drag process is like MetaBall ). After careful observation, we can find that two circles of different sizes are combined with two besell curves.
I have broken down the algorithm into another blog, it is strongly recommended that you do not know how the shape is drawn. Let's take a look at the detailed calculation method of "algorithm analysis" QQ "one-click return".
1. Draw and drag
Now that we know how to draw the coordinate points, we can implement them now.
First, create a new "canvas" inherited fromUIView
//AZMetaBallCanvas.h@interface AZMetaBallCanvas : UIView@property(nonatomic,strong) Circle *centerCircle;@property(nonatomic,strong) Circle *touchCircle;@end
Circle
For custom object classes, some basic attributes of the circle are defined, such as the Center Coordinate and radius.
Why create a new canvas?
Because the red dot can be dragged in full screen mode, do not look at it on QQ.Cell
But you can pull it to anotherCell
In this case, you need to give the little red dot enough position to draw, and then create a canvas to draw the little red dot action.
AZMetaBallCanvas
Currently, there are two attributes: two circles, one center circle and one touch circle. According to requirements, the center circle should remain unchanged. The touch circle will change with the position of the finger touch the screen, you need to draw the besell curve between the two circles to form the ball effect.
Next, start writing.AZMetaBallCanvas
Implementation
//AZMetaBallCanvas.m#define RADIUS 40.0@interface AZMetaBallCanvas() { UIBezierPath *_path; CGPoint _touchPoint;}@end@implementation AZMetaBallCanvas- (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; NSLog(@"initWithCorder"); if (self) { [self initData]; } return self;}- (void)initData { _touchCircle = [Circle initWithcenterPoint:self.center radius:RADIUS]; _centerCircle = [Circle initWithcenterPoint:self.center radius:RADIUS]; _touchPoint = self.center; NSLog(@"self.center (%f, %f)", self.center.x, self.center.y);}@end
Initialize the positions of two circles first. The default value isView
Ininit
,initWithFrame
,initWithCoder
Add the custom Initialization Method to the parent class constructor.initData
.
Override Plotting Method
As in AndroidonDraw()
, In iOSdrawRect
Can be rewritten and then called[view setNeedsDisplay]
.
- (void)drawRect:(CGRect)rect { _path = [[UIBezierPath alloc] init]; [self drawCenterCircle]; [self drawTouchCircle:_touchPoint]; [self drawBezierCurveWithCircle1:_centerCircle Circle2:_touchCircle];}
As mentioned in algorithm analysis, we only need to draw two circles (drawCenterCircle
,drawTouchCircle
) And the beiser curve connecting the two circles (drawBezierCurve
), The algorithm is actually a detailed calculation method of [algorithm analysis] QQ "one-click return"
Built-in beiser curve for iOSUIBezierPath
Its built-in Circle MethodaddArcWithCenter: radius: startAngle: endAngle: clockwise:
So we only need to call it!
# Pragma mark draw circle --- circle-(void) drawCenterCircle {[self drawCircle: _ path circle: _ centerCircle];}-(void) drawTouchCircle :( CGPoint) center {_ touchCircle. centerPoint = center; [self drawCircle: _ path circle: _ touchCircle];}-(void) drawCircle :( UIBezierPath *) path circle :( Circle *) circle {[_ path addArcWithCenter: circle. centerPoint radius: circle. radius startAngle: 0 endAngle: 360 clockwise: true]; [_ path fill]; [_ path stroke]; [_ path removeAllPoints];}
# Pragma mark draw curve --- draw the besell curve-(void) drawBezierCurveWithCircle1 :( Circle *) circle1 Circle2 :( Circle *) circle2 {float circle1_x = circle1.centerPoint. x; float circle1_y = circle1.centerPoint. y; float circle2_x = circle2.centerPoint. x; float circle2_y = circle2.centerPoint. y; // float d = sqrt (powf (circle1_x-circle2_x, 2) + powf (circle1_y-circle2_y, 2 )); // float angle1 = atan (circle2_y-circle1_y)/(circle1_x-circle2_x )); // float angle2 = asin (circle1.radius-circle2.radius); // float angle3 = M_PI_2-angle1-angle2; float angle4 = M_PI_2-angle1 + angle2; float placement = cos (angle3) * circle1.radius; float offset1_Y = sin (angle3) * circle1.radius; float placement = cos (angle3) * circle2.radius; float flood = sin (angle3) * circle2.radius; float offset3_X = cos (angle4) * circle1.radius; float flood = sin (angle4) * circle1.radius; float offset4_X = cos (angle4) * circle2.radius; float cursor = sin (angle4) * circle2.radius; float p1_x = relative-offset1_X; float p1_y = circle1_y-offset1_Y; float p2_x = circle2_x-offset2_X; float p2_y = relative-cursor; float p3_x = circle1_x + offset3_X; float p3_y = circle1_y + week; float p4_x = circle2_x + offset4_X; float p4_y = week + week; CGPoint p1 = CGPointMake (p1_x, p1_y ); CGPoint p2 = CGPointMake (p2_x, p2_y); CGPoint p3 = CGPointMake (p3_x, p3_y); CGPoint p4 = CGPointMake (p4_x, p4_y); CGPoint p1_center_p4 = CGPointMake (p1_x + p4_x) /2, (p1_y + p4_y)/2); CGPoint p2_center_p3 = CGPointMake (p2_x + p3_x)/2, (p2_y + p3_y)/2); [self drawBezierCurveStartAt: p1 EndAt: p2 controlPoint: p2_center_p3]; [self drawLineStartAt: p2 EndAt: p4]; [self outcome: p4 EndAt: p3 controlPoint: p1_center_p4]; [self drawLineStartAt: p3 EndAt: p1]; [_ path moveToPoint: p1]; [_ path closePath]; [_ path stroke];}
2. Simple listening gesture Edition
The simplest thing is to directlyAZMetaBallCanvas
RewritetouchXXX
And then callsetNeedsDisplay
NotificationUIView
Redraw.
#pragma mark touch event- (void)touchesBegan:(NSSet
*)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; _touchPoint = [touch locationInView:self]; [self setNeedsDisplay];}- (void)touchesMoved:(NSSet
*)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; _touchPoint = [touch locationInView:self]; [self setNeedsDisplay];}
In fact, the effect of the second graph has come out. The difference is to change the radius of the two circles.
The method to change the radius is very simple.
# Pragma change radius-(void) changeCenterCircleRadiusTo :( float) radius {_ centerCircle. radius = radius; [self setNeedsDisplay];}-(void) changeTouchCircleRadiusTo :( float) radius {_ touchCircle. radius = radius; [self setNeedsDisplay];}
Basic Edition
According to the phenomenon, we need to drag and drop the red dot to move it, instead of pointing to it, where the red dot is. Therefore, we need to add a gesture listener to the red dot, instead of a canvas ".
So we add a method to the canvas.- (void)attach:(UIView *)item;
And then add the imported viewPan
Gesture.
-(Void) attach :( UIView *) item {UIPanGestureRecognizer * drag = [[UIPanGestureRecognizer alloc] initWithTarget: self action: @ selector (drag :)]; item. userInteractionEnabled = YES; [item addGestureRecognizer: drag];}-(void) drag :( required *) recognizer {// get the touch point _ touchPoint = [recognizer locationInView: self]; // obtain the touch view UIView * touchView = recognizer. view; switch (recognizer. state) {case UIGestureRecognizerStateBegan: {// touch start: Draw a copy of The touchView on the canvas //... for this part, see the source code break;} case UIGestureRecognizerStateChanged: {// in motion: recording the touch position, changing the coordinates of the touchView and touchCircle [self resetTouchCenter: _ touchPoint]; break ;} case UIGestureRecognizerStateEnded: {// touch end: determines whether to execute an animation blast or a spring animation based on the length of the heartbeat line //... for this part, see the source code break;} default: break;} [self setNeedsDisplay]; // redraw}