效果如图:
鱼的最大数量可控制,运动速度随机,大小随机,摆尾速度随机,运动轨迹随机,气泡个数随机,气泡位置随机,出现时间随机。
参考文章:波浪效果
帧动画与UIView动画
气泡
1.先写一个波浪效果:TBMyInfoTopView.swift
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..
使用方法
2.加上旋转的阳光
更改上面的代码,更改后如下:
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
值得注意的是下面这部分代码当时遇到一点小坑
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()
}
}
使用
组合到上面的代码中需要将背景色改变成透明
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
使用
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