作用图

iOS视图的Material Design水波纹作用完结(附完结代码)

完结思路

在开始编写任何代码之前,咱们需求决议怎么完结这个作用。它会是某个特定UIView的子类吗?还是一种新的UIView类型,能够作为子视图增加?我选择了以上都不是,而是决议运用扩展(Category)来扩展UIView,这是一个简略而有效的办法。经过创立一个新的扩展,咱们能够无缝地将这个作用增加到任何UIView中,而不会遇到任何费事。

既然咱们知道了要在哪里编写代码,咱们需求为这个动画定义一个API。目前,这个完结将是简略的。关于动画,咱们需求定义几个要素:

  • 作为动画起点的方位。
  • 咱们想要的涟漪颜色。
  • 咱们期望涟漪有多大。
  • 咱们期望它扩展的速度,或者换句话说,咱们期望动画持续多长时间。
  • 涟漪何时开始淡出。

在本文的末尾,你能够找到一个包括所有这些代码的存储库链接,但让咱们先看看怎么构建代码。

咱们的UIView类别(Category)将如下所示:

#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(^JCNFlashButtonDidClickBlock)(void);
@interface UIView (JCNSimpleRipple)
/// clickCallback
@property (nonatomic, strong) JCNFlashButtonDidClickBlock clickCallback;
/// 开始波纹动画
/// - Parameters:
///  - origin: starting point for our animation
///  - color: The color we want our ripple to have.
///  - duration: how long we want our animation to last.
///  - radius: How big we want our ripple to be.
///  - fadeAfter: When the ripple should start fading.
///  - callback: 动画结束后的回调
- (void)rippleStartingAt:(CGPoint)origin
       withColor:(UIColor *)color
        duration:(NSTimeInterval)duration
         radius:(CGFloat)radius
       fadeAfter:(NSTimeInterval)fadeAfter
      clickCallbck:(JCNFlashButtonDidClickBlock)callback;
@end
NS_ASSUME_NONNULL_END

咱们的动画将在一个新的CAShapeLayer中进行。这个新的图层将坐落所有其他图层的后面,以确保它不会遮挡UIView的任何内容。然后,咱们将增加一个CABasicAnimation来对途径进行改换。这个途径将是实际的涟漪,它将从一个小小的圆形改换为咱们在参数中指定的巨细。

这是一个简化版的完结办法:

    CAShapeLayer *rippleLayer = [CAShapeLayer layer];
    UIBezierPath *startPath = [UIBezierPath bezierPathWithArcCenter:origin
                            radius:startRadius
                            startAngle:0 endAngle:FULL
                            clockwise:YES];
    UIBezierPath *endPath = [UIBezierPath bezierPathWithArcCenter:origin
                             radius:endRadius
                           startAngle:0 endAngle:FULL
                            clockwise:YES];
  CABasicAnimation *rippleAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
  rippleAnimation.fillMode = kCAFillModeBoth;
  rippleAnimation.duration = duration;
  rippleAnimation.fromValue = (id)(startPath.CGPath);
  rippleAnimation.toValue = (id)(endPath.CGPath);
  rippleAnimation.removedOnCompletion = NO;
  rippleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
  [self.layer insertSublayer:rippleLayer atIndex:0];
  [rippleLayer addAnimation:rippleAnimation forKey:nil];

关于淡出作用,咱们将以类似的方式处理:

CABasicAnimation *fadeOutAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeOutAnimation.fromValue = @1;
fadeOutAnimation.toValue = @0;
fadeOutAnimation.duration = fadeOutDuration;
fadeOutAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[rippleLayer addAnimation:fadeOutAnimation forKey:nil];

最终,一旦咱们完结动画,咱们能够从UIView中移除涟漪动画。可是有一个小问题。一般咱们会运用CAAnimationDelegate并完结适当的回调办法,可是咱们要么必须存储对新增加图层的引用,要么必须遍历子图层并找到相应的图层。这也意味着咱们不能(至少不容易)同时具有多个涟漪动画。因此,咱们将选用十分简略的解决方案,在动画完结时,咱们将排队执行一段代码块,并在该代码块中将涟漪图层移除。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, duration * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    [rippleLayer removeAllAnimations]; 
    [rippleLayer removeFromSuperlayer];
});

仓库链接: github.com/Yinjianhua4…