iOS开发 QQ粘性动画效果

QQ(iOS)客户端的粘性动画效果

时间 2016-02-17 16:50:00  博客园精华区

原文  http://www.cnblogs.com/ziyi--caolu/p/5195615.html

主题 iOS开发

qq的app中要是有新的联系人发消息过来,相应联系人的cell右边会有一个红色的圆圈表示消息条数。如果去触碰那个圆圈,可以发现它竟然会跟着手指的移动而移动。

在一定范围内,手指离开屏幕,会发现红色圆圈会自动弹性的回到原来的位置。而如果超出一定距离,这个圆圈会做一个销毁的动画,从而从view上移除掉。

产品要求公司的App也要有效果,并花了些时间去学习它的实现过程,发现其实原理还是比较简单的。

(由于mac制作gif图片实在过于麻烦,所以效果只能是看看图片。)

Demo的github地址:https://github.com/wzpziyi1/QQ-Goo

这是实现过程中的一些效果图片:

经过分析,可以发现,是两个圆和一个不规则矩形位置、大小的变化。一开始,小的圆圈和大的圆圈的center是相同的,当移动大圆的时候,小圆的半径随着大圆离小圆的距离变远而变小,当大圆距离小圆一定距离时,将小圆隐藏掉,中间的不规则矩形remove掉。

那么,不规则矩形怎么表示呢?可以利用Core Graphics在drawRect方法里面绘制不规则矩形的path,然后利用颜色fill就行。不规则矩形是随着大圆的移动而不断变化的,如果在drawRect方法里面绘制,那么在移动过程中不断调用setNeedsDisplay方法进行重绘。这是种可行的方案,我所用的也大致是这种思路。

不过,我没有在drawRect方法里面绘制,而是利用了CAShapeLayer,将不规则矩形的path绘制在shapeLayer里面,这样在移动大圆的过程中不断更新CAShapeLayer的path即可。

当然,难点并在在这里。而是不规则矩形的各个点的位置。要绘制这个不规则矩形,需要知道六个点的位置:

有了这些点的坐标,那么就可以用UIBezierPath来绘制相应的路径,代码如下:

- (UIBezierPath *)pathWithBigCircleView:(UIView *)bigCircleView smallCircleView:(UIView *)smallCircleView
{

    CGPoint bigCenter = bigCircleView.center;
    CGFloat x2 = bigCenter.x;
    CGFloat y2 = bigCenter.y;
    CGFloat r2 = bigCircleView.bounds.size.width / 2;

    CGPoint smallCenter = smallCircleView.center;
    CGFloat x1 = smallCenter.x;
    CGFloat y1 = smallCenter.y;
    CGFloat r1 = smallCircleView.bounds.size.width / 2;

    // 获取圆心距离
    CGFloat d = [self distanceWithPointA:bigCenter pointB:smallCenter];

    //Θ:(xita)
    CGFloat sinθ = (x2 - x1) / d;

    CGFloat cosθ = (y2 - y1) / d;

    // 坐标系基于父控件
    CGPoint pointA = CGPointMake(x1 - r1 * cosθ , y1 + r1 * sinθ);
    CGPoint pointB = CGPointMake(x1 + r1 * cosθ , y1 - r1 * sinθ);
    CGPoint pointC = CGPointMake(x2 + r2 * cosθ , y2 - r2 * sinθ);
    CGPoint pointD = CGPointMake(x2 - r2 * cosθ , y2 + r2 * sinθ);
    CGPoint pointO = CGPointMake(pointA.x + d / 2 * sinθ , pointA.y + d / 2 * cosθ);
    CGPoint pointP =  CGPointMake(pointB.x + d / 2 * sinθ , pointB.y + d / 2 * cosθ);

    UIBezierPath *path = [UIBezierPath bezierPath];

    // A
    [path moveToPoint:pointA];

    // AB
    [path addLineToPoint:pointB];

    // 绘制BC曲线
    [path addQuadCurveToPoint:pointC controlPoint:pointP];

    // CD
    [path addLineToPoint:pointD];

    // 绘制DA曲线
    [path addQuadCurveToPoint:pointA controlPoint:pointO];

    return path;
}

在实现过程中,我是自定义UIButton的,需要注意的是,在监听button的拖动时,最好是给它添加UIPanGestureRecognizer手势,而不要在touchesBegin方法里面去判断它的移动位置,因为Touches系列方法会屏蔽button的点击。

自定义的这个button默认就是大圆,包含一个小圆(UIView)属性,但是这个小圆并不是添加在自定义的这个button(也就是大圆)里面,而是在button的superView上。因为小圆并不需要随着大圆位置的改变而改变位置,相应的,shapeLayer也是添加在button(大圆)的父控件上。

给大圆添加了pan手势,在pan:方法里面随之改变小圆的大小和绘制shapeLayer的path。

当pan手势状态为End的时候,需要判断大圆与小圆的距离有没有超出最大距离,如果超过,那么添加一个gif图片,播放销毁大圆的过程。如果没有被销毁,那么大圆需要复位,相应代码:

#import "ZYGooView.h"

#define kMaxDistance 100

@interface ZYGooView ()
@property (nonatomic, weak) UIView *smallCircleView;

@property (nonatomic, assign) CGFloat smallCircleR;

@property (nonatomic, weak) CAShapeLayer *shapeLayer;
@end

@implementation ZYGooView

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        [self commitInit];

    }
    return self;
}

- (void)awakeFromNib
{
    [self commitInit];
}

- (void)commitInit
{
    self.layer.cornerRadius = self.frame.size.width * 0.5;
    self.layer.masksToBounds = YES;

    self.smallCircleR = self.frame.size.width * 0.5;
    self.smallCircleView.bounds = self.bounds;
    self.smallCircleView.center = self.center;
    self.smallCircleView.layer.cornerRadius = self.smallCircleView.frame.size.width * 0.5;

    [self addGesture];
}

#pragma mark ----懒加载方法

- (UIView *)smallCircleView
{
    if (_smallCircleView == nil) {
        UIView *view = [[UIView alloc] init];

        view.backgroundColor = self.backgroundColor;

        [self.superview addSubview:view];

        [self.superview insertSubview:view atIndex:0];

        _smallCircleView = view;

    }
    return _smallCircleView;
}

- (CAShapeLayer *)shapeLayer
{
    if (_shapeLayer == nil) {
        CAShapeLayer *shapeLayer = [CAShapeLayer layer];
        shapeLayer.path = [self pathWithBigCircleView:self smallCircleView:self.smallCircleView].CGPath;
        shapeLayer.fillColor = self.backgroundColor.CGColor;

        [self.superview.layer addSublayer:shapeLayer];

        [self.superview.layer insertSublayer:shapeLayer atIndex:0];

        _shapeLayer = shapeLayer;
    }
    return _shapeLayer;
}

#pragma mark ----其他方法

- (void)addGesture
{
    UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
    [self addGestureRecognizer:recognizer];
}

- (void)pan:(UIPanGestureRecognizer *)recognizer
{
    CGPoint point = [recognizer translationInView:self.superview];

    CGPoint center = self.center;
    center.x += point.x;
    center.y += point.y;
    self.center = center;
    //复位
    [recognizer setTranslation:CGPointZero inView:self];

    CGFloat distance = [self distanceWithPointA:self.smallCircleView.center pointB:self.center];

    if (distance == 0) return;

    CGFloat newR = self.smallCircleR - distance / 15.0;
    NSLog(@"%f", newR);
    self.smallCircleView.bounds = CGRectMake(0, 0, newR * 2, newR * 2);
    self.smallCircleView.layer.cornerRadius = newR;

    if (distance > kMaxDistance || newR <= 0) {
        self.smallCircleView.hidden = YES;
        [self.shapeLayer removeFromSuperlayer];
        self.shapeLayer = nil;
    }

    if (distance <= kMaxDistance && self.smallCircleView.hidden == NO) {
        self.shapeLayer.path = [self pathWithBigCircleView:self smallCircleView:self.smallCircleView].CGPath;
    }

    if (recognizer.state == UIGestureRecognizerStateEnded) {
        if (distance <= kMaxDistance) {

            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.03 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self.shapeLayer removeFromSuperlayer];
                self.shapeLayer = nil;
            });

            [UIView animateWithDuration:0.4 delay:0 usingSpringWithDamping:0.6 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{
                self.center = self.smallCircleView.center;

            } completion:^(BOOL finished) {
                self.smallCircleView.hidden = NO;
            }];
        }
        else {
            UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds];
            [self addSubview:imageView];

            NSMutableArray *images = [NSMutableArray array];

            for (int i = 1; i <= 8; i++) {
                NSString *imageName = [NSString stringWithFormat:@"%d", i];
                UIImage *image = [UIImage imageNamed:imageName];
                [images addObject:image];
            }

            imageView.animationImages = images;
            imageView.animationDuration = 0.6;
            imageView.animationRepeatCount = 1;
            [imageView startAnimating];

            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                [self removeFromSuperview];
            });
        }
    }
}

- (CGFloat)distanceWithPointA:(CGPoint)pointA pointB:(CGPoint)pointB
{
    CGFloat dx = pointB.x - pointA.x;
    CGFloat dy = pointB.y - pointA.y;

    return sqrt(dx * dx + dy * dy);
}

- (UIBezierPath *)pathWithBigCircleView:(UIView *)bigCircleView smallCircleView:(UIView *)smallCircleView
{

    CGPoint bigCenter = bigCircleView.center;
    CGFloat x2 = bigCenter.x;
    CGFloat y2 = bigCenter.y;
    CGFloat r2 = bigCircleView.bounds.size.width / 2;

    CGPoint smallCenter = smallCircleView.center;
    CGFloat x1 = smallCenter.x;
    CGFloat y1 = smallCenter.y;
    CGFloat r1 = smallCircleView.bounds.size.width / 2;

    // 获取圆心距离
    CGFloat d = [self distanceWithPointA:bigCenter pointB:smallCenter];

    //Θ:(xita)
    CGFloat sinθ = (x2 - x1) / d;

    CGFloat cosθ = (y2 - y1) / d;

    // 坐标系基于父控件
    CGPoint pointA = CGPointMake(x1 - r1 * cosθ , y1 + r1 * sinθ);
    CGPoint pointB = CGPointMake(x1 + r1 * cosθ , y1 - r1 * sinθ);
    CGPoint pointC = CGPointMake(x2 + r2 * cosθ , y2 - r2 * sinθ);
    CGPoint pointD = CGPointMake(x2 - r2 * cosθ , y2 + r2 * sinθ);
    CGPoint pointO = CGPointMake(pointA.x + d / 2 * sinθ , pointA.y + d / 2 * cosθ);
    CGPoint pointP =  CGPointMake(pointB.x + d / 2 * sinθ , pointB.y + d / 2 * cosθ);

    UIBezierPath *path = [UIBezierPath bezierPath];

    // A
    [path moveToPoint:pointA];

    // AB
    [path addLineToPoint:pointB];

    // 绘制BC曲线
    [path addQuadCurveToPoint:pointC controlPoint:pointP];

    // CD
    [path addLineToPoint:pointD];

    // 绘制DA曲线
    [path addQuadCurveToPoint:pointA controlPoint:pointO];

    return path;
}

@end
时间: 2024-12-28 08:57:49

iOS开发 QQ粘性动画效果的相关文章

ios开发之--简单动画效果的添加

记录一个简单的动画效果,自己写的,很简单,仅做记录. 附一个demo的下载地址: https://github.com/hgl753951/hglTest.git 代码如下: 1,准备 BOOL _isOpen; NSMutableArray * _btnArray; 2,具体代码 -(void)initUI { _btnArray = [[NSMutableArray alloc]init]; for (int i=0; i<4; i++) { UIButton * btn = [UIButt

iOS开发之吸附动画效果

步骤:1.使用singleviewapplication创建新的项目 2.在.h文件中创建两张图片的实例对象,并与相关的图片进行相连:创建一个UIDynamicAnimator实例对象 3.在.m文件中使用UIAttachmentBehavior创建吸附对象,调用 [UIAttachmentBehavior alloc] initWithItem:<#(nonnull id<UIDynamicItem>)#> attachedToAnchor:<#(CGPoint)#>

iOS开发--UIPickerView的动画效果

开发中用到UIPickerView显示和隐藏的动画.随手记一下 第一步:写一个动画方法 - (void)ViewAnimation:(UIView*)view willHidden:(BOOL)hidden { [UIView animateWithDuration:0.3 animations:^{ if (hidden) { view.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height, [UIScreen mainS

iOS 开发之推力动画效果

步骤: 1.使用single view application 创建新的项目 2.在.h文件中需要遵守两个协议<UICollisionBehaviorDelegate,UIGestureRecognizerDelegate>,使用UIimageview创建一个图片实例对象并与相关图片相连,使用UIDynamicAnimator创建一个动画实例对象,使用UIPushBehavior创建一个推力实例对象,使用cgpoint创建一个实例对象用来记录触碰的起始触点,用来确定推力的方向 3.在.m文件中

iOS开发QQ空间半透明效果的实现

//1.首先我们可以确定的是cell的半透明, /* white The grayscale value of the color object, specified as a value from 0.0 to 1.0. alpha The opacity value of the color object, specified as a value from 0.0 to 1.0. */ cell.backgroundColor = [UIColor colorWithWhite:1 alp

IOS开发系列 --- 核心动画

原始地址:http://www.cnblogs.com/kenshincui/p/3972100.html 概览 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌.在这里你可以看到iOS中如何使用图层精简非交互式绘图,如何通过核心动画创建基础动画.关键帧动画.动画组.转场动画,如何通过UIView的装饰方法对这些动画操作进行简化等.在今天的文章里您可以看到动画操作在iOS中是如何简单和高效,很多原来想做但是苦于没有思路的动画在iOS中将变得越发简

IOS视图缩放显示动画效果

效果:视图从大--小缩放显示/小--大 (只是比例问题) 方法1.直接show出view的时候:把下面的这段代码加到viewController或者view出现的时候就OK self.view.transform = CGAffineTransformMakeScale(1.0f, 1.0f);//将要显示的view按照正常比例显示出来  [UIView beginAnimations:nil context:UIGraphicsGetCurrentContext()];  [UIView se

iOS效果图---------------------qq粘性效果

这几天做了一些简单iOS的效果图,感觉苹果官方已经帮我们做了很多了,我们只是站在巨人的肩膀上编程,这些也没什么难的,最难的也就是用到了初中的三角函数,先让大家看看这几个动画吧.先列这几个把,由上而下分别是 数据缓冲效果 ,粒子动画,HUD指示效果,QQ未读消息的粘性效果,图一把一半遮住就是一种音乐播放器的播放效果,好了图一,图二, 图三都好简单就好似黄子华讲过,我只要一张嘴,两只手就可以把她搞得好嗨皮,而图一 图二,图三就是只要一个Animation,两个Layer就搞定了,而图四也不难,下面详

iOS开发之各种动画各种页面切面效果

因工作原因,有段时间没发表博客了,今天就发表篇博客给大家带来一些干货,切勿错过哦.今天所介绍的主题是关于动画的,在之前的博客中也有用到动画的地方,今天就好好的总结一下iOS开发中常用的动画.说道动画其中有一个是仿射变换的概念,至于怎么仿射的怎么变换的,原理如何等在本篇博客中不做赘述.今天要分享的是如和用动画做出我们要做的效果. 今天主要用到的动画类是CALayer下的CATransition至于各种动画类中如何继承的在这也不做赘述,网上的资料是一抓一大把.好废话少说切入今天的正题. 一.封装动画