動(dòng)畫在UI交互中是一種增強(qiáng)用戶體驗(yàn)的利器渗磅,目前看到幾乎每一個(gè)移動(dòng)App都會(huì)使用到各種動(dòng)畫效果嚷硫。
在IOS開發(fā)中實(shí)現(xiàn)動(dòng)畫效果通常有三種方式。
- 1始鱼、基于UIView仔掸,為了方便實(shí)現(xiàn)簡(jiǎn)單的動(dòng)畫封裝的UIView Animation。
- 2医清、基于CALayer的Core Animation框架起暮,這是動(dòng)畫的基礎(chǔ)框架。
- 3会烙、在游戲開發(fā)中經(jīng)常用到的基于物理模擬的動(dòng)畫框架UIKit Dynamics负懦。【未涉及柏腻,暫不講解】
先放置了一個(gè)View測(cè)試
#define Size(x) ((x)*[[UIScreen mainScreen] bounds].size.width/375.f)
#define kScreenHeight [[UIScreen mainScreen] bounds].size.height
#define kScreenWidth [[UIScreen mainScreen] bounds].size.width
@property (nonatomic, strong) UIView *greenView;
-(void)initCusView{
self.greenView = [[UIView alloc]initWithFrame:CGRectMake(0, Size(0), Size(100), Size(100))];
self.greenView.backgroundColor = UIColor.greenColor;
[self.view addSubview:self.greenView];
}
UIView Animation
UIView Animation 參數(shù)說明:
duration : 動(dòng)畫經(jīng)歷時(shí)長
delay : 延遲時(shí)間,在該延遲時(shí)間后才執(zhí)行動(dòng)畫
options : 系統(tǒng)提供了許多動(dòng)畫執(zhí)行的方式,比如以下幾個(gè)
enum {
//這部分是基礎(chǔ)屬性的設(shè)置
UIViewAnimationOptionLayoutSubviews = 1 << 0,//設(shè)置 子視圖隨父視圖展示動(dòng)畫
UIViewAnimationOptionAllowUserInteraction = 1 << 1,//允許在動(dòng)畫執(zhí)行時(shí)用戶與其進(jìn)行交互
UIViewAnimationOptionBeginFromCurrentState = 1 << 2,//允許在動(dòng)畫執(zhí)行時(shí)執(zhí)行新的動(dòng)畫
UIViewAnimationOptionRepeat = 1 << 3,//設(shè)置動(dòng)畫循環(huán)執(zhí)行
UIViewAnimationOptionAutoreverse = 1 << 4,//設(shè)置動(dòng)畫反向執(zhí)行纸厉,必須和重復(fù)執(zhí)行一起使用
UIViewAnimationOptionOverrideInheritedDuration = 1 << 5,//強(qiáng)制動(dòng)畫使用內(nèi)層動(dòng)畫的時(shí)間值
UIViewAnimationOptionOverrideInheritedCurve = 1 << 6,//強(qiáng)制動(dòng)畫使用內(nèi)層動(dòng)畫曲線值
UIViewAnimationOptionAllowAnimatedContent = 1 << 7,//設(shè)置動(dòng)畫視圖實(shí)時(shí)刷新
UIViewAnimationOptionShowHideTransitionViews = 1 << 8,//設(shè)置視圖切換時(shí)隱藏,而不是移除
UIViewAnimationOptionOverrideInheritedOptions = 1 << 9,//
//這部分屬性設(shè)置動(dòng)畫播放的線性效果
UIViewAnimationOptionCurveEaseInOut = 0 << 16,//淡入淡出 首末減速
UIViewAnimationOptionCurveEaseIn = 1 << 16,//淡入 初始減速
UIViewAnimationOptionCurveEaseOut = 2 << 16,//淡出 末尾減速
UIViewAnimationOptionCurveLinear = 3 << 16,//線性 勻速執(zhí)行
//這部分設(shè)置UIView切換效果(轉(zhuǎn)場(chǎng)動(dòng)畫使用)
UIViewAnimationOptionTransitionNone = 0 << 20,
UIViewAnimationOptionTransitionFlipFromLeft = 1 << 20,//從左邊切入
UIViewAnimationOptionTransitionFlipFromRight = 2 << 20,//從右邊切入
UIViewAnimationOptionTransitionCurlUp = 3 << 20,//從上面立體進(jìn)入
UIViewAnimationOptionTransitionCurlDown = 4 << 20,//從下面立體進(jìn)入
UIViewAnimationOptionTransitionCrossDissolve = 5 << 20,//溶解效果
UIViewAnimationOptionTransitionFlipFromTop = 6 << 20,//從上面切入
UIViewAnimationOptionTransitionFlipFromBottom = 7 << 20,//從下面切入
};
animation : UIView動(dòng)畫結(jié)束時(shí)的狀態(tài) ( 比如 : UIView移動(dòng)到另一點(diǎn),變成某一種顏色,放大(縮小)后的比例,變化到某一透明度,視圖旋轉(zhuǎn)到某一角度)
completion : 動(dòng)畫結(jié)束時(shí)的回調(diào)(這里可以處理一些事件)
usingSpringWithDamping : 阻尼(彈性系數(shù))
initialSpringVelocity : 初始速率
///基礎(chǔ)動(dòng)畫五嫂,結(jié)束無回調(diào)
[UIView animateWithDuration:1 animations:^{
}];
///基礎(chǔ)動(dòng)畫颗品,結(jié)束帶回調(diào)
[UIView animateWithDuration:1 animations:^{
} completion:^(BOOL finished) {
}];
///進(jìn)階動(dòng)畫--帶動(dòng)畫執(zhí)行的方式,結(jié)束帶回調(diào)
[UIView animateWithDuration:1 delay:1 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{
} completion:^(BOOL finished) {
}];
///進(jìn)階動(dòng)畫--可設(shè)置彈跳效果沃缘,結(jié)束帶回調(diào) 帶動(dòng)畫執(zhí)行的方式
[UIView animateWithDuration:1 delay:1 usingSpringWithDamping:0.11 initialSpringVelocity:1 options:UIViewAnimationOptionAutoreverse animations:^{
} completion:^(BOOL finished) {
}];
可以用來做什么呢:
設(shè)置UIView的屬性:例如
frame
bounds
center
transform
alpha
backgroundColor
contentStretch
看一下實(shí)例:
self.greenView = [[UIView alloc]initWithFrame:CGRectMake(Size(30), Size(100), Size(100), Size(100))];
self.greenView.backgroundColor = UIColor.greenColor;
[self.view addSubview:self.greenView];
///開始動(dòng)畫
-(void)startAnimation{
[UIView animateWithDuration:1 animations:^{
///縮放比例
self.greenView.transform = CGAffineTransformScale(CGAffineTransformIdentity, 1.5, 1.5);
///位置調(diào)整
CGRect rect = self.greenView.frame;
rect.origin.x += Size(50);
rect.origin.y += Size(50);
self.greenView.frame = rect;
///透明度變化
self.greenView.alpha = 0.5;
///color變化
self.greenView.backgroundColor = UIColor.redColor;
///圓角改變
self.greenView.layer.cornerRadius = Size(50);
self.greenView.clipsToBounds = YES;
}];
}
我們可以看到self.greenView通過UIView Animation動(dòng)畫將某些屬性進(jìn)行了改變躯枢。
現(xiàn)在我們適當(dāng)?shù)募尤胍恍﹦?dòng)畫執(zhí)行的方式【options】
UIViewAnimationOptionRepeat:持續(xù)重復(fù)動(dòng)畫內(nèi)容
UIViewAnimationOptionCurveEaseIn:淡入
UIViewAnimationOptionCurveEaseOut:淡出
UIViewAnimationOptionCurveEaseInOut:淡入淡出
UIViewAnimationOptionCurveLinear:勻速運(yùn)動(dòng)
[UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
CGRect rect = self.greenView.frame;
rect.origin.x += Size(275);
self.greenView.frame = rect;
} completion:^(BOOL finished) {
}];
在開發(fā)中可以添加特定的options滿足不同的動(dòng)畫需要。
彈簧效果:
[UIView animateWithDuration:3 delay:0 usingSpringWithDamping:0.1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveLinear animations:^{
CGRect rect = self.greenView.frame;
rect.origin.x += Size(275);
self.greenView.frame = rect;
} completion:^(BOOL finished) {
}];
CABasicAnimation
CABasicAnimation 為layer屬性提供了基礎(chǔ)的幀動(dòng)畫能力孩灯,創(chuàng)建一個(gè)CABasicAnimation的實(shí)例闺金,使用繼承自CAPropertyAnimation的animationWithKeyPath:方法,來指定要添加動(dòng)畫的layer屬性的keypath
CABasicAnimation常用的有如下幾個(gè)屬性:
//動(dòng)畫改變屬性
@property(nullable, copy) NSString *keyPath;
// 指定執(zhí)行動(dòng)畫layer的起始值
@property(nullable, strong) id fromValue;
// 指定結(jié)束動(dòng)畫layer的結(jié)束值
@property(nullable, strong) id toValue;
// 指定執(zhí)行動(dòng)畫的時(shí)候的相對(duì)值
@property(nullable, strong) id byValue;
CABasicAnimation 相關(guān)常用屬性
/* 基礎(chǔ)動(dòng)畫開始時(shí)刻 CACurrentMediaTime() + 秒數(shù) 默認(rèn)為0.0 這個(gè)屬性在組動(dòng)畫中很有用 它根據(jù)父動(dòng)畫組的持續(xù)時(shí)間,指定了開始播放動(dòng)畫的時(shí)間* /
@property CFTimeInterval beginTime;
/* 基礎(chǔ)動(dòng)畫的重復(fù)次數(shù) 默認(rèn)為0.0*/
@property float repeatCount;
/*畫應(yīng)該被重復(fù)多久峰档。動(dòng)畫會(huì)一直重復(fù),直到設(shè)定的時(shí)間流逝完急凰。它不應(yīng)該和 repeatCount 一起使用*/
@property CFTimeInterval repeatDuration;
/* 基礎(chǔ)動(dòng)畫的時(shí)間間隔 默認(rèn)為0.25*/
@property CFTimeInterval duration;
/* 設(shè)置為YES的時(shí)候锁摔,當(dāng)動(dòng)畫時(shí)間間隔過了后,動(dòng)畫就會(huì)移除*/
@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;
/* 當(dāng)動(dòng)畫時(shí)間間隔后,指定動(dòng)畫對(duì)象的表現(xiàn)胁附。
* 本地時(shí)間可能在時(shí)間間隔后或者是元素從當(dāng)前表現(xiàn)層移除后被壓縮
* 合法的值有kCAFillModeForwards、kCAFillModeBackwards劝堪、kCAFillModeBoth kCAFillModeRemoved
*/
@property(copy) NSString *fillMode;
//設(shè)定動(dòng)畫的速度變化 默認(rèn):nil 使用寫法:[CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseInEaseOut];
@property(nullable, strong) CAMediaTimingFunction *timingFunction;
//動(dòng)畫結(jié)束時(shí)執(zhí)行逆動(dòng)畫
@property BOOL autoreverses;
CABasicAnimation的寫法墓怀。
移動(dòng)動(dòng)畫
讓一個(gè)view向左平移,在x方向上從屏幕x中間線型移動(dòng)到左邊消失,耗時(shí)1.5秒的動(dòng)畫
第一種方法:
// 創(chuàng)建動(dòng)畫 使用動(dòng)畫改變屬性 position.x
CABasicAnimation *positionAnima = [CABasicAnimation animationWithKeyPath:@"position.x"];
// 指定基礎(chǔ)動(dòng)畫的時(shí)間間隔炼七,以秒為單位默認(rèn)值是 0.25
positionAnima.duration = 1.5;
//改變前的屬性
positionAnima.fromValue = @((kScreenWidth-Size(100))/2);
//改變后的屬性
positionAnima.toValue = @(-self.greenView.frame.size.width / 2);
//保存最新狀態(tài)
positionAnima.fillMode = kCAFillModeForwards;
//動(dòng)畫組件不被移除
positionAnima.removedOnCompletion = NO;
[self.greenView.layer addAnimation:positionAnima forKey:@"position.x"];
第二種方法:
// 創(chuàng)建動(dòng)畫
CABasicAnimation *positionAnima = [CABasicAnimation animation];
// 動(dòng)畫改變屬性
positionAnima.keyPath = @"position";
positionAnima.duration = 1.5;
// 改變后的屬性
positionAnima.toValue = [NSValue valueWithCGPoint:CGPointMake(-Size(50), Size(100+50))];
// 動(dòng)畫組件不被移除
positionAnima.removedOnCompletion = NO;
// 保存最新狀態(tài)
positionAnima.fillMode = kCAFillModeForwards;
[self.greenView.layer addAnimation:positionAnima forKey:@"position"];
區(qū)別:設(shè)置的keypath不同第一種方法指定了左移缆巧,第二種方法可以是上下左右移動(dòng),fromValue豌拙、toValue需要根據(jù)keypath屬性改變
旋轉(zhuǎn)動(dòng)畫
5s完成旋轉(zhuǎn)一周的順時(shí)針旋轉(zhuǎn)的view 并持續(xù)旋轉(zhuǎn)
CABasicAnimation * rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 ];
rotationAnimation.duration = 5;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = HUGE_VAL;
rotationAnimation.removedOnCompletion = NO;
[self.greenView.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
縮放動(dòng)畫
/* 放大縮小 */
// 設(shè)定為縮放
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
// 動(dòng)畫選項(xiàng)設(shè)定
animation.duration = 2.5; // 動(dòng)畫持續(xù)時(shí)間
animation.repeatCount = 1; // 重復(fù)次數(shù)
animation.autoreverses = YES; // 動(dòng)畫結(jié)束時(shí)執(zhí)行逆動(dòng)畫
// 縮放倍數(shù)
animation.fromValue = [NSNumber numberWithFloat:1.0]; // 開始時(shí)的倍率
animation.toValue = [NSNumber numberWithFloat:2.0]; // 結(jié)束時(shí)的倍率
// 添加動(dòng)畫
[self.greenView.layer addAnimation:animation forKey:@"scale-layer"];
KeyPath的改變動(dòng)畫的效果就不一樣陕悬,開發(fā)中改變KeyPath的屬性可以實(shí)現(xiàn)大多數(shù)我們需要的動(dòng)畫執(zhí)行的效果
animationWithKeyPath值類型:
position = 路徑(一般用CAKeyframeAnimation)
transform.rotation = 旋轉(zhuǎn)
transform.rotation.x = x旋轉(zhuǎn)
transform.rotation.y = y旋轉(zhuǎn)
transform.rotation.z = z旋轉(zhuǎn)(順逆時(shí)針)
transform.translation = 平移
transform.translation.x = x平移
transform.translation.y = y平移
transform.scale = 比例轉(zhuǎn)換
transform.scale.x = x的比例轉(zhuǎn)換
transform.scale.y = y的比例轉(zhuǎn)換
transform.scale.y = y的比例轉(zhuǎn)換
transform.rotation.z = 平面圓的轉(zhuǎn)換
opacity = 透明度
margin
zPosition
backgroundColor = 背景顏色
cornerRadius = 圓角
borderWidth
bounds
contents
contentsRect
cornerRadius
frame 坐標(biāo)
hidden 隱藏
mask
masksToBounds
shadowColor
shadowOffset
shadowOpacity
shadowRadius
大家可以嘗試使用不同的keypath看看動(dòng)畫效果
用CABasicAnimation執(zhí)行動(dòng)畫,在動(dòng)畫結(jié)束后會(huì)回歸動(dòng)畫開始前的狀態(tài)按傅。想要解決的話捉超,必須設(shè)置“removedOnCompletion”和“fillMode”這兩個(gè)屬性。
// 動(dòng)畫終了后不返回初始狀態(tài)
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
但是
由于在開發(fā)過程中光是CABasicAnimation的fromValue唯绍、toValue起點(diǎn)和終點(diǎn)設(shè)置是無法滿足我們希望在動(dòng)畫中途進(jìn)行更多的變化的需求拼岳,所以我們需要認(rèn)識(shí)一下CAKeyframeAnimation
CAKeyframeAnimation[關(guān)鍵幀動(dòng)畫]
從上面的繼承圖我們看出CAKeyframeAnimation 比CABasicAnimation多了更多的可設(shè)置屬性
/* 提供關(guān)鍵幀數(shù)據(jù)的數(shù)組,數(shù)組中的每一個(gè)值都對(duì)應(yīng)一個(gè)關(guān)鍵幀况芒。根據(jù)動(dòng)畫類型(keyPath)的不同 惜纸,
值的類型不同*/
@property(nullable, copy) NSArray *values;
/*基于點(diǎn)的屬性的路徑,即動(dòng)畫屬性類型為CGPoint。如: position牛柒、anchorPoint堪簿、transform.translation等
如果為此屬性指定非空值,則會(huì)忽略values屬性*/
@property(nullable) CGPathRef path;
/* keyTimes的值與values中的值一一對(duì)應(yīng)指定關(guān)鍵幀在動(dòng)畫中的時(shí)間點(diǎn)皮壁,取值范圍為[0,1]椭更。當(dāng)keyTimes沒有設(shè)置的時(shí)候,
各個(gè)關(guān)鍵幀的時(shí)間是平分的*/
@property(nullable, copy) NSArray*keyTimes;
/*指定每個(gè)關(guān)鍵幀之間的動(dòng)畫緩沖效果,timingFunctions.count = keyTimes.count-1*/
@property(nullable, copy) NSArray*timingFunctions;
/*關(guān)鍵幀間插值計(jì)算模式*/
@property(copy) NSString *calculationMode;
/*針對(duì)cubic 計(jì)算模式的動(dòng)畫蛾魄,這些屬性提供對(duì)插值方案的控制虑瀑。每個(gè)*關(guān)鍵幀都可以具有與之相關(guān)的
張力、連續(xù)性和偏差值滴须,這些值的范圍在[-1,1]內(nèi)(這定義了Kochanek-*Bartels樣條舌狗,見http://en.wikipedia.org/wiki/Kochanek-Bartels_spline)。
*tensionValues控制曲線的“緊密度”(正值更緊扔水,負(fù)值更圓)痛侍。
*continuityValues控制段的連接方式(正值表示銳角,負(fù)值表示倒角)魔市。
*biasValues定義曲線發(fā)生的位置(正值在控制點(diǎn)之前移動(dòng)曲線主届,負(fù)值在控制點(diǎn)之后移動(dòng)它)。
*每個(gè)數(shù)組中的第一個(gè)值定義第一個(gè)控制點(diǎn)的切線的行為待德,第二個(gè)值控*制第二個(gè)點(diǎn)的切線君丁,依此類推。任何未指定的值都默認(rèn)為零
*(如果未指定将宪,則給出Catmull-Rom樣條曲線)绘闷。
*/
@property(nullable, copy) NSArray*tensionValues;
@property(nullable, copy) NSArray*continuityValues;
@property(nullable, copy) NSArray *biasValues;
/*定義沿路徑動(dòng)畫的對(duì)象是否旋轉(zhuǎn)以匹配路徑切線*/
@property(nullable, copy) NSString *rotationMode;
關(guān)鍵幀動(dòng)畫其實(shí)通過一組動(dòng)畫類型的值(或者一個(gè)指定的路徑)和這些值對(duì)應(yīng)的時(shí)間節(jié)點(diǎn)以及各時(shí)間節(jié)點(diǎn)的過渡方式來控制顯示的動(dòng)畫橡庞。關(guān)鍵幀動(dòng)畫可以通過path屬性和values屬性來設(shè)置動(dòng)畫的關(guān)鍵幀。
通過path設(shè)置動(dòng)畫
繞線一周動(dòng)畫
CGMutablePathRef path = CGPathCreateMutable();
//第一個(gè)關(guān)鍵幀 -100印蔗,-100
CGPathMoveToPoint(path, NULL, 0, 0);
//第二個(gè)關(guān)鍵幀 100扒最,-100
CGPathAddLineToPoint(path, NULL, 100, 0);
//第三個(gè)關(guān)鍵幀 100,100
CGPathAddLineToPoint(path, NULL, 100, 100);
//第四個(gè)關(guān)鍵幀 -100华嘹,100
CGPathAddLineToPoint(path, NULL, 0, 100);
//第五個(gè)關(guān)鍵幀 -100扼倘,-100
CGPathAddLineToPoint(path, NULL, 0, 0);
CAKeyframeAnimation *animation = [CAKeyframeAnimation animation];
animation.keyPath = @"transform.translation";
animation.path = path;
animation.duration = 4;
animation.keyTimes = @[@(0),@(0.1),@(0.5),@(0.75),@(1)];
animation.timingFunctions = @[[CAMediaTimingFunction functionWithControlPoints:1 :0.5 :0.5 :0.5],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut],
[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
//動(dòng)畫結(jié)束后保持動(dòng)畫最后的狀態(tài),兩個(gè)屬性需配合使用
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
CGPathRelease(path);
[self.greenView.layer addAnimation:animation forKey:@""];
通過values設(shè)置動(dòng)畫
/// 放大縮小放大縮小【隱藏】
CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale"];
animation.duration = 2.0;// 動(dòng)畫時(shí)間
animation.removedOnCompletion = NO;
animation.values = @[@1,@1.2,@1,@1.2,@0];
//動(dòng)畫結(jié)束后保持動(dòng)畫最后的狀態(tài)除呵,兩個(gè)屬性需配合使用
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeForwards;
[self.greenView.layer addAnimation:animation forKey:nil];
CAAnimationGroup[動(dòng)畫組]
可以保存一組動(dòng)畫CAKeyframeAnimation、CABasicAnimation對(duì)象爪喘,將CAAnimationGroup對(duì)象加入圖層后颜曾,組中所有動(dòng)畫對(duì)象可以同時(shí)并發(fā)運(yùn)行。
購物車動(dòng)畫
/* 動(dòng)畫1(在XY軸方向移動(dòng)) */
CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"transform.translation"];
// 終點(diǎn)設(shè)定
animation1.toValue = [NSValue valueWithCGPoint:CGPointMake(kScreenWidth-Size(50), kScreenHeight-Size(50))]; // 終點(diǎn)
/* 動(dòng)畫2(繞Z軸中心旋轉(zhuǎn)) */
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
// 設(shè)定旋轉(zhuǎn)角度
animation2.fromValue = [NSNumber numberWithFloat:0.0]; // 開始時(shí)的角度
animation2.toValue = [NSNumber numberWithFloat:4 * M_PI]; // 結(jié)束時(shí)的角度
/* 動(dòng)畫組 */
CAAnimationGroup *group = [CAAnimationGroup animation];
// 動(dòng)畫選項(xiàng)設(shè)定
group.duration = 3.0;
group.repeatCount = 1;
// 添加動(dòng)畫
group.animations = [NSArray arrayWithObjects:animation1, animation2, nil];
[self.greenView.layer addAnimation:group forKey:@"move-rotate-layer"];
注意:默認(rèn)情況下秉剑,一組動(dòng)畫對(duì)象是同時(shí)運(yùn)行的泛豪,也可以通過設(shè)置單個(gè)動(dòng)畫對(duì)象的beginTime屬性來更改動(dòng)畫的開始時(shí)間,單個(gè)動(dòng)畫的執(zhí)行效果可以與動(dòng)畫組執(zhí)行效果屬性分開設(shè)定侦鹏,根據(jù)需要調(diào)整改變诡曙。