在公司的项目基础上,今天也实验了下,QQ未读数拖拽效果。之前就看到网上有同志分析怎么做这个动画,我这里就不写原理了, 大家想看原理的,在网上搜搜就是了,很简单。 我这里只把效果和代码粘贴一下。所涉及到的公司的业务逻辑已经删除掉,此Demo只是用来交流学习使用。
#define kBtnWidth self.bounds.size.width
#define kBtnHeight self.bounds.size.height
@interface SouFunNoReadNumIconView()
/** 绘制不规则图形 */
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
/** 小圆 */
@property (nonatomic, strong) UIView *samllCircleView;
/** 文案 */
@property (nonatomic, copy) NSString *noReadStr;
/** 类型 */
@property (nonatomic, assign) E_RedIconType type;
/** 父视图 */
@property (nonatomic, weak) UIView *mySuperView;
/** 记录父视图 */
@property (nonatomic, weak) UIView *restSuperView;
/** 记录原点 */
@property (nonatomic, assign) CGPoint originPoint;
/** 大圆脱离小圆的最大距离 */
@property (nonatomic, assign) CGFloat maxDistance;
@end
@implementation SouFunNoReadNumIconView
- (instancetype) initWithContentStr:(NSString *)noReadStr andType:(E_RedIconType)type andLRPoint:(CGPoint)leftUpPoint andSuperView:(UIView *)mySuperView{
if (self = [super init]) {
self.type = type;
self.noReadStr = noReadStr;
self.mySuperView = mySuperView;
if (type == E_Normal) {
CGSize size = [self sizeWithContentStr:noReadStr];
self.frame = CGRectMake(leftUpPoint.x, leftUpPoint.y, size.width, size.height);
self.numberOfLines = 1;
self.backgroundColor = [UtilityHelper colorWithHexString:@"#ff0000"];
self.textColor = [UIColor whiteColor];
[self setTextAlignment:NSTextAlignmentCenter];
[self setFont:[UIFont systemFontOfSize:12.0f]];
[self setText:noReadStr];
self.layer.cornerRadius = 9.f;
self.layer.masksToBounds = YES;
[mySuperView addSubview:self];
[self setUp];
}else if (type == E_Normal_Wode) {
CGSize size = [self sizeWithContentStr:noReadStr];
self.frame = CGRectMake(leftUpPoint.x, leftUpPoint.y, size.width, size.height);
self.numberOfLines = 1;
self.backgroundColor = [UIColor whiteColor];
self.textColor = [UtilityHelper colorWithHexString:@"#ff2429"];
[self setTextAlignment:NSTextAlignmentCenter];
[self setFont:[UIFont systemFontOfSize:12.0f]];
[self setText:noReadStr];
self.layer.cornerRadius = 9.f;
self.layer.masksToBounds = YES;
}else{//留待扩展其他类型
}
}
return self;
}
- (instancetype) initWithContentStr:(NSString *)noReadStr andType:(E_RedIconType)type andLRPoint:(CGPoint)leftUpPoint{
if (self = [super init]) {
self.type = type;
self.noReadStr = noReadStr;
if (type == E_Normal) {
CGSize size = [self sizeWithContentStr:noReadStr];
self.frame = CGRectMake(leftUpPoint.x, leftUpPoint.y, size.width, size.height);
self.numberOfLines = 1;
self.backgroundColor = [UtilityHelper colorWithHexString:@"#ff0000"];
self.textColor = [UIColor whiteColor];
[self setTextAlignment:NSTextAlignmentCenter];
[self setFont:[UIFont systemFontOfSize:12.0f]];
[self setText:noReadStr];
self.layer.cornerRadius = 9.f;
self.layer.masksToBounds = YES;
[self setUp];
}else if (type == E_Normal_Wode) {
CGSize size = [self sizeWithContentStr:noReadStr];
self.frame = CGRectMake(leftUpPoint.x, leftUpPoint.y, size.width, size.height);
self.numberOfLines = 1;
self.backgroundColor = [UIColor whiteColor];
self.textColor = [UtilityHelper colorWithHexString:@"#ff2429"];
[self setTextAlignment:NSTextAlignmentCenter];
[self setFont:[UIFont systemFontOfSize:12.0f]];
[self setText:noReadStr];
self.layer.cornerRadius = 9.f;
self.layer.masksToBounds = YES;
}else{//留待扩展其他类型
}
}
return self;
}
//根据内容获取size
- (CGSize)sizeWithContentStr:(NSString *)contentStr{
CGSize size;
if ([contentStr isEqualToString:@"99+"]) {
size = CGSizeMake(30, 18);
}else if ([contentStr integerValue]<10 &&[contentStr integerValue]>0){
size = CGSizeMake(18, 18);
}else if ([contentStr integerValue]>=10 &&[contentStr integerValue]<=99){
size = CGSizeMake(24, 18);
}else{
size = CGSizeMake(0, 0);
}
return size;
}
//小圆
- (UIView *)samllCircleView
{
if (!_samllCircleView) {
_samllCircleView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 18 , 18)];
_samllCircleView.backgroundColor = [UtilityHelper colorWithHexString:@"#ff0000"];
[self.superview insertSubview:_samllCircleView belowSubview:self];
}
return _samllCircleView;
}
//shapeLayer
- (CAShapeLayer *)shapeLayer
{
if (!_shapeLayer) {
_shapeLayer = [CAShapeLayer layer];
_shapeLayer.fillColor = self.backgroundColor.CGColor;
[self.superview.layer insertSublayer:_shapeLayer below:self.layer];
}
return _shapeLayer;
}
//setUp
- (void)setUp
{
self.userInteractionEnabled = YES;
CGFloat cornerRadius = (kBtnHeight > kBtnWidth ? kBtnWidth / 2.0 : kBtnHeight / 2.0);
_maxDistance = cornerRadius * 6;
self.samllCircleView.center = self.center;
self.samllCircleView.layer.cornerRadius = self.samllCircleView.bounds.size.width / 2.0;
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
[self addGestureRecognizer:pan];
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)];
[self addGestureRecognizer:longPress];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
[self addGestureRecognizer:tap];
}
- (void)pan:(UIPanGestureRecognizer *)pan
{
[self.layer removeAnimationForKey:@"shake"];
if (pan.state == UIGestureRecognizerStateBegan) {
self.restSuperView = pan.view.superview;
self.originPoint = pan.view.center;
self.tableViewBase.utilityTableView.scrollEnabled = NO;
}
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
CGPoint translation = [pan translationInView:window];
CGPoint topFloorPoint = [pan.view.superview convertPoint:pan.view.center toView:window];
CGPoint panPoint = CGPointMake(topFloorPoint.x+ translation.x,
topFloorPoint.y + translation.y);
pan.view.center= panPoint;
[pan setTranslation:CGPointZero inView:window];
if (pan.state == UIGestureRecognizerStateBegan) {
CGPoint topFloorPoint1 = [pan.view.superview convertPoint:self.samllCircleView.center toView:window];
self.samllCircleView.center = topFloorPoint1;
self.samllCircleView.hidden = NO;
[window addSubview:self.samllCircleView];
[window addSubview:self];
}
//俩个圆的中心点之间的距离
CGFloat dist = [self pointToPoitnDistanceWithPoint:self.center potintB:self.samllCircleView.center];
if (dist < _maxDistance) {
CGFloat cornerRadius = (kBtnHeight > kBtnWidth ? kBtnWidth / 2 : kBtnHeight / 2);
CGFloat samllCrecleRadius = cornerRadius - dist / 10;
_samllCircleView.bounds = CGRectMake(0, 0, samllCrecleRadius * (2 - 0.5), samllCrecleRadius * (2 - 0.5));
_samllCircleView.layer.cornerRadius = _samllCircleView.bounds.size.width / 2;
if (_samllCircleView.hidden == NO && dist > 0) {
//画不规则矩形
self.shapeLayer.path = [self pathWithBigCirCleView:self smallCirCleView:_samllCircleView].CGPath;
}
} else {
[self.shapeLayer removeFromSuperlayer];
self.shapeLayer = nil;
self.samllCircleView.hidden = YES;
}
if (pan.state == UIGestureRecognizerStateEnded) {
if (dist > _maxDistance) {
self.tableViewBase.utilityTableView.scrollEnabled = YES;
[self killAll];
[SouFunNoReadNumIconView updateDataBaseWithType:self.cleanNoReadtype andMessageid:self.messageId];
} else {
[self.shapeLayer removeFromSuperlayer];
self.shapeLayer = nil;
[UIView animateWithDuration:0.3 delay:0 usingSpringWithDamping:0.2 initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.center = self.samllCircleView.center;
} completion:^(BOOL finished) {
self.samllCircleView.hidden = NO;
[self.restSuperView addSubview:self.samllCircleView];
[self.restSuperView addSubview:self];
self.center = self.originPoint;
self.samllCircleView.center = self.originPoint;
self.tableViewBase.utilityTableView.scrollEnabled = YES;
}];
}
}
}
//销毁全部控件
- (void)killAll
{
[self removeFromSuperview];
[self.samllCircleView removeFromSuperview];
self.samllCircleView = nil;
[self.shapeLayer removeFromSuperlayer];
self.shapeLayer = nil;
}
// 俩个圆心之间的距离
- (CGFloat)pointToPoitnDistanceWithPoint:(CGPoint)pointA potintB:(CGPoint)pointB
{
CGFloat offestX = pointA.x - pointB.x;
CGFloat offestY = pointA.y - pointB.y;
CGFloat dist = sqrtf(offestX * offestX + offestY * offestY);
return dist;
}
//不规则路径
- (UIBezierPath *)pathWithBigCirCleView:(UIView *)bigCirCleView smallCirCleView:(UIView *)smallCirCleView
{
CGPoint bigCenter = bigCirCleView.center;
CGFloat x2 = bigCenter.x;
CGFloat y2 = bigCenter.y;
CGFloat r2 = bigCirCleView.bounds.size.height / 2.0;
CGPoint smallCenter = smallCirCleView.center;
CGFloat x1 = smallCenter.x;
CGFloat y1 = smallCenter.y;
CGFloat r1 = smallCirCleView.bounds.size.width / 2.0;
// 获取圆心距离
CGFloat d = [self pointToPoitnDistanceWithPoint:self.samllCircleView.center potintB:self.center];
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];
[path moveToPoint:pointA];
[path addLineToPoint:pointB];
[path addQuadCurveToPoint:pointC controlPoint:pointP];
[path addLineToPoint:pointD];
[path addQuadCurveToPoint:pointA controlPoint:pointO];
return path;
}
//设置长按时候左右摇摆的动画
- (void)longPress:(UILongPressGestureRecognizer *)longPress
{
[self.layer removeAnimationForKey:@"shake"];
if (longPress.state == UIGestureRecognizerStateBegan) {
//长按左右晃动的幅度大小
CGFloat shake = 6;
CAKeyframeAnimation *keyAnim = [CAKeyframeAnimation animation];
keyAnim.keyPath = @"transform.translation.x";
keyAnim.values = @[@(-shake), @(shake), @(-shake)];
keyAnim.removedOnCompletion = NO;
keyAnim.repeatCount = MAXFLOAT;
//左右晃动一次的时间
keyAnim.duration = 0.3;
[self.layer addAnimation:keyAnim forKey:@"shake"];
}
}
- (void)tap:(UIGestureRecognizer *)tap{
tap.view.hidden = YES;
self.samllCircleView.hidden = YES;
NSInteger rowClocn = 3;
NSMutableArray *boomCells = [NSMutableArray array];
for (int i = 0; i < rowClocn*rowClocn; ++ i) {
CGFloat pw = MIN(self.frame.size.width, self.frame.size.height);
CALayer *shape = [CALayer layer];
shape.backgroundColor = [UIColor colorWithRed:231/255.0 green:231/255.0 blue:231/255.0 alpha:1.0].CGColor;
shape.cornerRadius = pw / 2;
shape.frame = CGRectMake(0, 0, pw, pw);
[self.layer.superlayer addSublayer: shape];
[boomCells addObject:shape];
}
[self cellAnimations:boomCells withGesture:tap];
}
- (void)cellAnimations:(NSArray*)cells withGesture:(UIGestureRecognizer*)gesture{
for (NSInteger j=0; j<cells.count;j++) {
CALayer *shape = cells[j];
shape.position = self.center;
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
scaleAnimation.toValue = @0.6;
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
pathAnimation.path = [self makeRandomPath: shape AngleTransformation:j].CGPath;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseOut];
animationGroup.animations = @[scaleAnimation,pathAnimation,];
animationGroup.fillMode = kCAFillModeForwards;
animationGroup.duration = 0.5;
animationGroup.removedOnCompletion = NO;
animationGroup.repeatCount = 1;
[shape addAnimation: animationGroup forKey: @"animationGroup"];
[self performSelector:@selector(removeLayer:) withObject:shape afterDelay:animationGroup.duration];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[gesture.view removeFromSuperview];
[[self class] updateDataBaseWithType:self.cleanNoReadtype andMessageid:self.messageId];
});
}
- (UIBezierPath *) makeRandomPath: (CALayer *) alayer AngleTransformation:(CGFloat)index{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:self.center];
CGFloat dia = self.frame.size.width * (10-(arc4random()%3))/10.0;
if (index>0) {
CGFloat angle = index*45*M_PI*2/360;
CGFloat x = dia*cosf(angle);
CGFloat y = dia*sinf(angle);
[path addLineToPoint:CGPointMake(self.center.x + x, self.center.y+y)];
}else{
[path addLineToPoint:CGPointMake(self.center.x, self.center.y)];
}
return path;
}
-(void)removeLayer:(CALayer*)layer{
[layer removeAnimationForKey:@"animationGroup"];
layer.hidden = YES;
[layer removeFromSuperlayer];
}
+(void)updateDataBaseWithType:(E_CleanNoReadType)type andMessageid:(NSString *)messageid{
//已经删除未读数具体业务逻辑
}
@end