【iOS效果集】實現QQ消除小紅點(一鍵退朝)效果

來源:互聯網
上載者:User

【iOS效果集】實現QQ消除小紅點(一鍵退朝)效果

QQ上黏黏的小紅點很好玩有木有,於是自己也想實現一番,看到iOS實現的人比較少,Android的比較多,於是這個就用iOS來實現哈~

調試圖:

其實從實現來講,我是先實現第二張圖的效果的。

實現思路

1.瞭解原理,以及如何繪製“黏黏”形狀(即繪製兩圓加兩條貝茲路徑)。

2.建立UIView(AZMetaBallCanvas),作為單獨畫布用來繪製“黏黏”形狀,用程式實現演算法,並繪製出來。

3.給畫布(AZMetaBallCanvas)添加attach:(UIView *)方法,並添加手勢監聽,重繪,使得任意 view 都能夠被在畫布上擁有“黏黏”效果。

4.根據連心線的距離加上判斷是否要斷開,使用者手指離開時也要根據距離來判斷是爆炸動畫還是回彈動畫。

詳細過程

首先必須要瞭解小紅點拖拽的過程形狀是什麼,其實就是類似元球效果(MetaBall)。仔細觀察可分析發現,就是兩個大小不一樣的圓加上兩條貝茲路徑構成的。

關於演算法部分,我已經分解成了另外一篇博文,強烈建議不清楚該形狀是怎麼畫出來的同學先看一下《【演算法分析】QQ“一鍵退朝”之詳細計算方法》

1.繪製拖拽

既然怎麼求座標點畫出來我們已經知道了,現在就可以去實現了。

首先建立一個“畫布”,繼承自UIView

//AZMetaBallCanvas.h@interface AZMetaBallCanvas : UIView@property(nonatomic,strong) Circle *centerCircle;@property(nonatomic,strong) Circle *touchCircle;@end

Circle為自訂實體類,裡面定義了一些圓的基本屬性,如圓心座標、半徑等。

為什麼要建立一個畫布?

因為小紅點是能夠全屏拖動的,別看QQ上它存在某一行Cell,但其實你可以把它拉到別的Cell上去,這就需要給小紅點足夠的位置來繪製,就乾脆建立一個畫布專門用來繪製小紅點的動作好了。

AZMetaBallCanvas目前包含兩個屬性,兩個圓,一個中心圓,一個觸摸圓,按照需求來看,中心圓應該是位置不變的,觸摸圓會跟隨手指觸控螢幕幕的位置而改變,後面需要在兩個圓之間畫上貝茲路徑來構成元球效果。

接下來開始寫AZMetaBallCanvas的實現

//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

先初始化兩個圓的位置,預設在View的中心,並在initinitWithFrameinitWithCoder等父類建構函式中加入自訂初始化方法initData

重寫繪製方法

如同Android中的onDraw(),iOS中的drawRect能夠被重寫繪製,然後調用[view setNeedsDisplay]來通知重繪。

- (void)drawRect:(CGRect)rect {    _path = [[UIBezierPath alloc] init];    [self drawCenterCircle];    [self drawTouchCircle:_touchPoint];    [self drawBezierCurveWithCircle1:_centerCircle Circle2:_touchCircle];}

如同演算法分析中所講,在繪製的時候,我們只需要繪製兩個圓(drawCenterCircledrawTouchCircle)和串連兩圓的貝茲路徑(drawBezierCurve),演算法其實就是照抄《【演算法分析】QQ“一鍵退朝”之詳細計算方法》

iOS內建貝茲路徑UIBezierPath,其內建畫圓方法addArcWithCenter: radius: startAngle: endAngle: clockwise:,所以我們只要調用就好啦!

#pragma mark draw 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 --- 畫貝茲路徑- (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));    //連心線x軸的夾角    float angle1 = atan((circle2_y - circle1_y) / (circle1_x - circle2_x));    //連心線和公切線的夾角    float angle2 = asin((circle1.radius - circle2.radius) / d);    //切點到圓心和x軸的夾角    float angle3 = M_PI_2 - angle1 - angle2;    float angle4 = M_PI_2 - angle1 + angle2;    float offset1_X = cos(angle3) * circle1.radius;    float offset1_Y = sin(angle3) * circle1.radius;    float offset2_X = cos(angle3) * circle2.radius;    float offset2_Y = sin(angle3) * circle2.radius;    float offset3_X = cos(angle4) * circle1.radius;    float offset3_Y = sin(angle4) * circle1.radius;    float offset4_X = cos(angle4) * circle2.radius;    float offset4_Y = sin(angle4) * circle2.radius;    float p1_x = circle1_x - offset1_X;    float p1_y = circle1_y - offset1_Y;    float p2_x = circle2_x - offset2_X;    float p2_y = circle2_y - offset2_Y;    float p3_x = circle1_x + offset3_X;    float p3_y = circle1_y + offset3_Y;    float p4_x = circle2_x + offset4_X;    float p4_y = circle2_y + offset4_Y;    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 drawBezierCurveStartAt:p4 EndAt:p3 controlPoint:p1_center_p4];    [self drawLineStartAt:p3 EndAt:p1];    [_path moveToPoint:p1];    [_path closePath];    [_path stroke];}
2.監聽手勢簡單版

最簡單的其實就是直接在AZMetaBallCanvas中重寫touchXXX等一系列方法,然後在其中調用setNeedsDisplay通知UIView重繪。

#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];}

現在其實差不多第二張圖的效果已經出來了,差的就是更改兩圓的半徑方法。

改變半徑的方法就非常簡單了

#pragma 改變半徑-(void)changeCenterCircleRadiusTo:(float)radius {    _centerCircle.radius = radius;    [self setNeedsDisplay];}-(void)changeTouchCircleRadiusTo:(float)radius {    _touchCircle.radius = radius;    [self setNeedsDisplay];}
普通版

根據現象發現,我們需要通過拖拽小紅點來移動它,而不是我們手指點哪,小紅點就在哪,所以我們需要給小紅點增加手勢監聽,而不是“畫布”。

於是我們改為在畫布添加方法- (void)attach:(UIView *)item;,然後再給傳入的view添加Pan手勢。

- (void)attach:(UIView *)item {    UIPanGestureRecognizer *drag = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(drag:)];    item.userInteractionEnabled = YES;    [item addGestureRecognizer:drag];}- (void)drag:(UIPanGestureRecognizer *)recognizer {    //得到觸摸點    _touchPoint = [recognizer locationInView:self];    //得到觸摸的view    UIView *touchView = recognizer.view;    switch (recognizer.state) {        case UIGestureRecognizerStateBegan:{            //touch開始:在畫布上繪製一個touchView的副本            //...此部分參看源碼            break;        }        case UIGestureRecognizerStateChanged:{            //移動中:記錄觸摸位置,更改touchView和touchCircle的座標位置            [self resetTouchCenter:_touchPoint];            break;        }        case UIGestureRecognizerStateEnded: {            //touch結束:根據連心線長度判斷是執行爆炸動畫還是彈簧動畫            //...此部分參看源碼            break;        }        default:            break;    }    [self setNeedsDisplay]; //重繪}

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

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.