iOS使用动画构建海底世界

效果如图:


鱼的最大数量可控制,运动速度随机,大小随机,摆尾速度随机,运动轨迹随机,气泡个数随机,气泡位置随机,出现时间随机。

参考文章:波浪效果
     帧动画与UIView动画
     气泡

1.先写一个波浪效果:TBMyInfoTopView.swift

iOS使用动画构建海底世界_第1张图片

import UIKit
class TBMyInfoTopView: UIView {
    
    //提供给外部调用接口
    
    ///曲线振幅
    var waterAmplitude: CGFloat = 8
    ///曲线角速度
    var waterPalstance: CGFloat = 0
    ///曲线初相
    var waterX: CGFloat = 0
    ///曲线偏距
    let waterY: CGFloat = 100
    ///曲线移动速度
    var waterMoveSpeed: CGFloat = 0
    
    //内部接口

    fileprivate let waterColor = UIColor(red: 118/255.0, green: 165/255.0, blue: 242/255.0, alpha: 0.6)
    fileprivate let BackGroundColor = UIColor(red: 80/255.0, green: 140/255.0, blue: 238/255.0, alpha: 1)
    //前面的波浪
    fileprivate let waterLayer1 = CAShapeLayer()
    fileprivate let waterLayer2 = CAShapeLayer()
    fileprivate var disPlayLink = CADisplayLink()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = BackGroundColor
        buildInterface()
        buildData()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func buildInterface(){
        //初始化波浪
        waterLayer1.fillColor = waterColor.cgColor
        waterLayer1.strokeColor = waterColor.cgColor
        waterLayer1.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        
        waterLayer2.fillColor = waterColor.cgColor
        waterLayer2.strokeColor = waterColor.cgColor
        waterLayer2.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        
        layer.addSublayer(waterLayer1)
        layer.addSublayer(waterLayer2)
    }
    
    ///初始化数据
    func buildData(){
        waterPalstance = CGFloat(Double.pi) / bounds.size.width
        waterMoveSpeed = 5 * waterPalstance
        //以屏幕刷新速度为周期刷新曲线的位置
        disPlayLink = CADisplayLink(target: self, selector: #selector(self.updatewater(link:)))
        disPlayLink.add(to: RunLoop.main, forMode: .commonModes)
    }
    
    func updatewater(link: CADisplayLink){
        //更新x
        waterX += waterMoveSpeed
        updatewater(shapLayer: waterLayer1, isSin: false)
        updatewater(shapLayer: waterLayer2, isSin: true)
    }
    
    func updatewater(shapLayer: CAShapeLayer, isSin: Bool){
        //波浪宽度
        let waterwaterWidth = bounds.size.width
        //初始化运动路径
        let path = CGMutablePath()
        //设置起始位置
        path.move(to: CGPoint(x: 0, y: waterY))
        //初始化波浪,y为偏距
        var tempY = waterY
        //正弦曲线公式为: y=Asin(ωx+φ)+k
        for x in 0..

使用方法

iOS使用动画构建海底世界_第2张图片

2.加上旋转的阳光

iOS使用动画构建海底世界_第3张图片

更改上面的代码,更改后如下:

import UIKit

class TBMyInfoTopView: UIView {
    
    //提供给外部调用接口
    
    ///曲线振幅
    var waterAmplitude: CGFloat = 8
    ///曲线角速度
    var waterPalstance: CGFloat = 0
    ///曲线初相
    var waterX: CGFloat = 0
    ///曲线偏距
    let waterY: CGFloat = 70
    ///曲线移动速度
    var waterMoveSpeed: CGFloat = 0

    fileprivate let waterColor = UIColor(red: 118/255.0, green: 165/255.0, blue: 242/255.0, alpha: 0.6)
    fileprivate let BackGroundColor = UIColor(red: 80/255.0, green: 140/255.0, blue: 238/255.0, alpha: 1)
    //前面的波浪
    fileprivate let waterLayer1 = CAShapeLayer()
    fileprivate let waterLayer2 = CAShapeLayer()
    fileprivate var disPlayLink = CADisplayLink()
    
    //阳光
    var sunshinView = UIImageView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = BackGroundColor
        buildInterface()
        buildData()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func buildInterface(){
        //初始化波浪
        waterLayer1.fillColor = waterColor.cgColor
        waterLayer1.strokeColor = waterColor.cgColor
        waterLayer1.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        
        waterLayer2.fillColor = waterColor.cgColor
        waterLayer2.strokeColor = waterColor.cgColor
        waterLayer2.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        
        layer.addSublayer(waterLayer1)
        layer.addSublayer(waterLayer2)
        
        //阳光
        sunshinView = UIImageView(frame: CGRect(x: 200, y: -100, width: 200, height: 200))
        sunshinView.image = #imageLiteral(resourceName: "gx.png")
        addSubview(sunshinView)
        // 创建动画
        let anim = CABasicAnimation(keyPath: "transform.rotation")
        // 设置动画属性
        anim.toValue = 2 * Double.pi
        anim.repeatCount = MAXFLOAT
        anim.duration = 20
        anim.isRemovedOnCompletion = false
        // 将动画添加到图层上
        sunshinView.layer.add(anim, forKey: nil)
    }
    
    ///初始化数据
    func buildData(){
        waterPalstance = CGFloat(Double.pi) / bounds.size.width
        waterMoveSpeed = 5 * waterPalstance
        //以屏幕刷新速度为周期刷新曲线的位置
        disPlayLink = CADisplayLink(target: self, selector: #selector(self.updatewater(link:)))
        disPlayLink.add(to: RunLoop.main, forMode: .commonModes)
    }
    
    func updatewater(link: CADisplayLink){
        //更新x
        waterX += waterMoveSpeed
        updatewater(shapLayer: waterLayer1, isSin: false)
        updatewater(shapLayer: waterLayer2, isSin: true)
    }
    
    func updatewater(shapLayer: CAShapeLayer, isSin: Bool){
        //波浪宽度
        let waterwaterWidth = bounds.size.width
        //初始化运动路径
        let path = CGMutablePath()
        //设置起始位置
        path.move(to: CGPoint(x: 0, y: waterY))
        //初始化波浪,y为偏距
        var tempY = waterY
        //正弦曲线公式为: y=Asin(ωx+φ)+k
        for x in 0..

3.用帧动画创建游动的鱼群FishView.swift

iOS使用动画构建海底世界_第4张图片

值得注意的是下面这部分代码当时遇到一点小坑

iOS使用动画构建海底世界_第5张图片

arc4random() % UInt32(x)中x必须大于1,而 abs(frame.height - 35)之类的结果是有可能小于1的,所以需要做相应处理,不能直接 arc4random() % UInt32(abs(frame.height - 35))

import UIKit

class FishView: UIView {
    
    var fishMaxNum = 5

    fileprivate var imageViewArray = [UIImageView]()
    fileprivate var timer: Timer!
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        for _ in 0..= 1 ? tempX : 1)
            let tempY = abs(frame.height - 35)
            let y = arc4random() % UInt32(tempY >= 1 ? tempY : 1)
            let w = arc4random() % 50 + 10
            let h = arc4random() % 30 + 5
            let imageView = UIImageView(frame: CGRect(x: CGFloat(x), y: CGFloat(y), width: CGFloat(w), height: CGFloat(h)))
            addSubview(imageView)
            imageViewArray.append(imageView)
        }
        
        self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(self.timerAction(timer:)), userInfo: nil, repeats: true)
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func timerAction(timer:Timer){
        let count = imageViewArray.count
        //随机要动的鱼数量
        let num = arc4random() % UInt32(count)
        for _ in 0.. 0 && tempX + frameOr.x < frame.width - imageView.frame.width{
            imageView.stopAnimating()
            if tempX + frameOr.x > imageView.frame.origin.x{
                animation(isLeft: false, imageView: imageView)
            }else{
                animation(isLeft: true, imageView: imageView)
            }
            UIView.animate(withDuration: TimeInterval(arc4random() % 5 + 1), animations: {
                imageView.frame.origin.x += tempX
            })
        }
        if tempY + frameOr.y > 0 && tempY + frameOr.y < frame.height - imageView.frame.height{
            UIView.animate(withDuration: TimeInterval(arc4random() % 5 + 1), animations: {
                imageView.frame.origin.y += tempY
            })
        }
    }
    
    func animation(isLeft: Bool, imageView: UIImageView){
        imageView.animationImages = []
        for item in 0...9{
            let imageName = isLeft ? "fishl\(item).png" : "fish\(item).png"
            let image = UIImage(named: imageName)
            imageView.animationImages?.append(image!)
            
        }
        imageView.animationDuration = Double((arc4random() % 20) + 5) / 10.0
        imageView.startAnimating()
    }

}

使用

iOS使用动画构建海底世界_第6张图片

组合到上面的代码中需要将背景色改变成透明

4.气泡

气泡搬了个砖,然后稍作修改,代码是OC写的,如下
TBBubbleView.h

#import 

@interface TBBubbleView : UIView

@property (nonatomic, assign)CGFloat maxLeft;//漂浮左边最大距离
@property (nonatomic, assign)CGFloat maxRight;//漂浮右边最大距离
@property (nonatomic, assign)CGFloat maxHeight;//漂浮最高距离
@property (nonatomic, assign)CGFloat duration;//一组图片播放完的时间
@property (nonatomic, copy)NSArray *images;//图片数组


//init
-(instancetype)initWithFrame:(CGRect)frame
                folatMaxLeft:(CGFloat)maxLeft
               folatMaxRight:(CGFloat)maxRight
              folatMaxHeight:(CGFloat)maxHeight
                bubbleNum:(NSInteger)bubbleNum;

//开始动画
-(void)startBubble;

@end

TBBubbleView.m

#import "TBBubbleView.h"

@interface TBBubbleView()
@property(nonatomic,strong)NSTimer *timer;

@property(nonatomic,assign)CGFloat maxWidth;

@property(nonatomic,assign)CGPoint startPoint;

@property(nonatomic,strong) NSMutableArray *layerArray;

@property(nonatomic) NSUInteger bubbleNum;

@property(nonatomic) NSInteger tempNum;

@end


@implementation TBBubbleView


//初始化
-(instancetype)initWithFrame:(CGRect)frame folatMaxLeft:(CGFloat)maxLeft folatMaxRight:(CGFloat)maxRight folatMaxHeight:(CGFloat)maxHeight bubbleNum:(NSInteger)bubbleNum
{
    self = [super initWithFrame:frame];
    if(self)
    {
        _maxLeft = maxLeft;
        _maxRight = maxRight;
        _maxHeight = maxHeight;
        _layerArray = [NSMutableArray array];
        _bubbleNum = bubbleNum;
        _tempNum = 0;
    }
    return self;
}
//外部方法 开始气泡
-(void)startBubble
{
    self.timer = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(generateBubble) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop]addTimer:self.timer forMode:UITrackingRunLoopMode];
}

-(void)generateBubble
{
    if (_tempNum == _bubbleNum) {
        [self.timer invalidate];
        return;
    }
    CALayer *layer =[CALayer layer];;
    UIImage *image = self.images[arc4random() % self.images.count];
    
    layer = [self createLayerWithImage:image];
    [self.layer addSublayer:layer];
    [self generateBubbleByLayer:layer];
    _tempNum += 1;
}

//创建带有Image的Layer
- (CALayer *)createLayerWithImage:(UIImage *)image
{
    CGFloat scale = [UIScreen mainScreen].scale;
    CALayer *layer = [CALayer layer];
    layer.frame    = CGRectMake(0, 0, image.size.width / scale, image.size.height / scale);
    layer.contents = (__bridge id)image.CGImage;
    return layer;
}


-(void)generateBubbleByLayer:(CALayer*)layer
{
    _maxWidth = _maxLeft + _maxRight;
    _startPoint = CGPointMake(self.frame.size.width/2, 0);
    
    CGPoint endPoint = CGPointMake(_maxWidth * [self randomFloat] - _maxLeft, -_maxHeight);
    
    CGPoint controlPoint1 =
    CGPointMake(_maxWidth * [self randomFloat] - _maxLeft, -_maxHeight * 0.2);
    CGPoint controlPoint2 =
    CGPointMake(_maxWidth * [self randomFloat] - _maxLeft, -_maxHeight * 0.6);
    
    CGMutablePathRef curvedPath = CGPathCreateMutable();
    CGPathMoveToPoint(curvedPath, NULL, _startPoint.x, _startPoint.y);
    CGPathAddCurveToPoint(curvedPath, NULL, controlPoint1.x, controlPoint1.y, controlPoint2.x, controlPoint2.y, endPoint.x, endPoint.y);
    UIBezierPath *path = [UIBezierPath bezierPathWithCGPath:curvedPath];
    //[path addCurveToPoint:endPoint controlPoint1:_startPoint controlPoint2:controlPoint1];
    
    CAKeyframeAnimation *keyFrame = [CAKeyframeAnimation animation];
    keyFrame.keyPath = @"position";
    keyFrame.path = path.CGPath;
    keyFrame.duration = self.duration;
    keyFrame.calculationMode = kCAAnimationPaced;
    
    [layer addAnimation:keyFrame forKey:@"keyframe"];
    
    
    CABasicAnimation *scale = [CABasicAnimation animation];
    scale.keyPath = @"transform.scale";
    scale.toValue = @1;
    scale.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 0.1)];
    scale.duration = 0.5;
    
    CABasicAnimation *alpha = [CABasicAnimation animation];
    alpha.keyPath = @"opacity";
    alpha.fromValue = @1;
    alpha.toValue = @0;
    alpha.duration = self.duration * 0.4;
    alpha.beginTime = self.duration - alpha.duration;
    
    CAAnimationGroup *group = [CAAnimationGroup animation];
    group.animations = @[keyFrame, scale, alpha];
    group.duration = self.duration;
    group.delegate = self;
    group.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    group.fillMode = kCAFillModeForwards;
    group.removedOnCompletion = NO;
    [layer addAnimation:group forKey:@"group"];
    
    [self.layerArray addObject:layer];
}
-(void)dealloc
{
    [self.layerArray removeAllObjects];
    [self.timer invalidate];
}

-(void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    if (flag)
    {
        CALayer *layer = [self.layerArray firstObject];
        [layer removeAllAnimations];
        [layer removeFromSuperlayer];
        [self.layerArray removeObject:layer];
        if (self.layerArray.count == 0) {
            [self removeFromSuperview];
        }
    }
    
}
- (CGFloat)randomFloat{
    return (arc4random() % 100)/100.0f;
}

@end

使用

iOS使用动画构建海底世界_第7张图片

5.组合

组合后TBMyInfoTopView文件如下

import UIKit

class TBMyInfoTopView: UIView {
    
    //提供给外部调用接口
    
    ///曲线振幅
    var waterAmplitude: CGFloat = 8
    ///曲线角速度
    var waterPalstance: CGFloat = 0
    ///曲线初相
    var waterX: CGFloat = 0
    ///曲线偏距,越小距离顶端越近
    let waterY: CGFloat = 50
    ///曲线移动速度
    var waterMoveSpeed: CGFloat = 0

    fileprivate let waterColor = UIColor(red: 118/255.0, green: 165/255.0, blue: 242/255.0, alpha: 0.3)
    fileprivate let BackGroundColor = UIColor(red: 80/255.0, green: 140/255.0, blue: 238/255.0, alpha: 1)
    //前面的波浪
    fileprivate let waterLayer1 = CAShapeLayer()
    fileprivate let waterLayer2 = CAShapeLayer()
    fileprivate var disPlayLink = CADisplayLink()
    
    //阳光
    var sunshinView = UIImageView()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = BackGroundColor
        buildInterface()
        buildData()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func buildInterface(){
        
        //珊瑚
        let shImageView = UIImageView(frame: CGRect(x: frame.width - 60, y: frame.height - 60, width: 60, height: 60))
        shImageView.image = #imageLiteral(resourceName: "shanhu.png")
        addSubview(shImageView)
        let scImageView = UIImageView(frame: CGRect(x: 150, y: frame.height - 40, width: 40, height: 40))
        scImageView.image = #imageLiteral(resourceName: "shuicao.png")
        addSubview(scImageView)
        
        sendSubview(toBack: scImageView)
        
        //初始化波浪
        waterLayer1.fillColor = waterColor.cgColor
        waterLayer1.strokeColor = waterColor.cgColor
        waterLayer1.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        
        waterLayer2.fillColor = waterColor.cgColor
        waterLayer2.strokeColor = waterColor.cgColor
        waterLayer2.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
        
        layer.addSublayer(waterLayer1)
        layer.addSublayer(waterLayer2)
        
        //阳光
        sunshinView = UIImageView(frame: CGRect(x: 200, y: -100, width: 200, height: 200))
        sunshinView.image = #imageLiteral(resourceName: "gx.png")
        addSubview(sunshinView)
        // 创建动画
        let anim = CABasicAnimation(keyPath: "transform.rotation")
        // 设置动画属性
        anim.toValue = 2 * Double.pi
        anim.repeatCount = MAXFLOAT
        anim.duration = 20
        anim.isRemovedOnCompletion = false
        // 将动画添加到图层上
        sunshinView.layer.add(anim, forKey: nil)
        
        //鱼群
        let fishView = FishView(frame: CGRect(x: 0, y: waterY + 10, width: frame.width, height: frame.height - waterY - 10))
        fishView.backgroundColor = UIColor.clear
        insertSubview(fishView, belowSubview: shImageView)
        Timer.scheduledTimer(timeInterval: TimeInterval(arc4random() % 10 + 5), target: self, selector: #selector(self.showBubble(timer:)), userInfo: nil, repeats: false)
    }
    
    ///初始化数据
    func buildData(){
        waterPalstance = CGFloat(Double.pi) / bounds.size.width
        waterMoveSpeed = 5 * waterPalstance
        //以屏幕刷新速度为周期刷新曲线的位置
        disPlayLink = CADisplayLink(target: self, selector: #selector(self.updatewater(link:)))
        disPlayLink.add(to: RunLoop.main, forMode: .commonModes)
    }
    
    func updatewater(link: CADisplayLink){
        //更新x
        waterX += waterMoveSpeed
        updatewater(shapLayer: waterLayer1, isSin: false)
        updatewater(shapLayer: waterLayer2, isSin: true)
    }
    
    func updatewater(shapLayer: CAShapeLayer, isSin: Bool){
        //波浪宽度
        let waterwaterWidth = bounds.size.width
        //初始化运动路径
        let path = CGMutablePath()
        //设置起始位置
        path.move(to: CGPoint(x: 0, y: waterY))
        //初始化波浪,y为偏距
        var tempY = waterY
        //正弦曲线公式为: y=Asin(ωx+φ)+k
        for x in 0..

Demo

你可能感兴趣的:(iOS使用动画构建海底世界)