在iOS开发中,如果不想通过直接修改视图的frame
属性来实现位移动画,有十多种方法,总结如下:
方法一:使用CABasicAnimation
可以使用Core Animation的CABasicAnimation
类或UIView动画块来实现。下面分别展示这两种方法。
1.CABasicAnimation
CABasicAnimation
可以用于对视图的图层属性进行动画化。通过更改图层的position
属性,可以实现位移动画而不直接修改视图的frame
。
示例代码:
#import <QuartzCore/QuartzCore.h>
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
// 创建位移动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.fromValue = [NSValue valueWithCGPoint:view.layer.position];
animation.toValue = [NSValue valueWithCGPoint:CGPointMake(300, 300)];
animation.duration = 1.0;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// 应用动画到图层
[view.layer addAnimation:animation forKey:@"positionAnimation"];
// 最终位置
view.layer.position = CGPointMake(300, 300);
2. CAKeyframeAnimation
CAKeyframeAnimation
允许我们通过指定一系列的关键帧来实现复杂的动画效果。它可以用于改变图层的position
属性,从而实现视图的移动。
示例代码:
#import <QuartzCore/QuartzCore.h>
// 创建一个视图
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
// 创建关键帧动画
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
// 设置起点和终点
CGPoint startPoint = view.layer.position;
CGPoint endPoint = CGPointMake(300, 300);
// 创建路径
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, startPoint.x, startPoint.y);
CGPathAddLineToPoint(path, NULL, endPoint.x, endPoint.y);
// 将路径赋值给动画
animation.path = path;
animation.duration = 2.0;
// 添加动画到图层
[view.layer addAnimation:animation forKey:@"moveAnimation"];
// 释放路径
CGPathRelease(path);
分析
-
创建视图:首先创建一个视图并添加到当前视图控制器的视图中。
-
创建关键帧动画 :使用
CAKeyframeAnimation
创建一个关键帧动画,并指定要改变的属性为position
。 -
设置路径:创建一个路径,从起点到终点。路径可以是直线、曲线等,这里我们使用直线。
-
将路径赋值给动画 :将创建的路径赋值给动画的
path
属性,并设置动画的持续时间。 -
添加动画到图层:将动画添加到视图的图层中。
方法二:使用UIView动画块
使用UIView动画块可以更方便地实现位移动画,且不需要直接操作图层。
示例代码:
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
// 使用UIView动画块
[UIView animateWithDuration:1.0
delay:0
options:UIViewAnimationOptionCurveEaseInOut
animations:^{
// 改变视图的中心点
view.center = CGPointMake(300, 300);
}
completion:nil];
注意事项
-
动画完成后的位置:
- 在使用
CABasicAnimation
时,需要在动画结束后手动设置图层的最终位置,因为动画只是改变了图层的"表现"而不是实际的属性值。 - 在使用UIView动画块时,动画结束后视图的位置会被自动更新。
- 在使用
-
动画的keyPath:
- 在
CABasicAnimation
中,keyPath
决定了动画作用的属性。对于位移动画,通常使用"position"
或"transform.translation"
。
- 在
-
图层的隐式动画:
UIView
实际上是CALayer
的封装,所有视图的动画都是通过图层实现的。直接使用图层动画可以实现更精细的控制。
通过这两种方法,可以在不直接修改视图frame
的情况下实现位移动画,从而实现更灵活和精细的动画效果。根据实际需求选择合适的方法,可以使动画代码更简洁和易于维护。
方法三: 利用锚点
利用锚点(anchorPoint)来实现位移动画是一种较为高级的技术。锚点定义了视图旋转和缩放的中心点,通过改变锚点,同时调整视图的position
属性,可以实现视图的位移动画,而不直接修改视图的frame
。
以下是使用anchorPoint
结合position
属性实现位移动画的示例代码:
示例代码
#import <QuartzCore/QuartzCore.h>
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
// 保存当前的中心点
CGPoint originalCenter = view.center;
// 目标位置
CGPoint targetCenter = CGPointMake(300, 300);
// 计算新锚点和新位置
CGPoint newAnchorPoint = CGPointMake((targetCenter.x - view.frame.origin.x) / view.frame.size.width,
(targetCenter.y - view.frame.origin.y) / view.frame.size.height);
// 创建位移动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"];
animation.fromValue = [NSValue valueWithCGPoint:view.layer.position];
animation.toValue = [NSValue valueWithCGPoint:targetCenter];
animation.duration = 1.0;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// 应用动画到图层
[view.layer addAnimation:animation forKey:@"positionAnimation"];
// 更改锚点后,重新设置视图的中心点
view.layer.anchorPoint = newAnchorPoint;
view.layer.position = targetCenter;
解释
-
保存当前中心点:
- 在更改
anchorPoint
之前,保存视图当前的center
,以便在更改锚点后重新调整视图的位置。
- 在更改
-
计算新锚点和新位置:
- 计算新锚点值。新锚点的计算基于目标位置相对于视图原始位置的比例。
-
创建位移动画:
- 使用
CABasicAnimation
创建一个position
属性的动画,从当前的position
到目标位置。
- 使用
-
应用动画到图层:
- 将动画应用到视图的图层。
-
更改锚点后,重新设置视图的中心点:
- 更改锚点为新计算的锚点,然后更新图层的
position
到目标位置。
- 更改锚点为新计算的锚点,然后更新图层的
注意事项
-
动画完成后的位置:
- 在动画完成后,需要手动修改视图的
anchorPoint
和position
,以确保视图在最终位置正确显示。
- 在动画完成后,需要手动修改视图的
-
锚点和位置的关系:
- 更改锚点会影响视图的
position
属性,因此在更改锚点后,需要重新计算和设置视图的位置。
- 更改锚点会影响视图的
-
复杂性:
- 使用锚点进行位移动画比直接修改
frame
或使用UIView动画块要复杂,适用于需要更精细控制动画和视图位置的情况。
- 使用锚点进行位移动画比直接修改
通过这种方法,可以利用锚点实现位移动画,从而在不直接修改视图frame
的情况下实现视图的移动。这种方法提供了更大的灵活性,特别是在需要精确控制视图位置和动画效果时。
方法四: 使用定时器(Timer)
可以使用NSTimer
或者GCD
的定时器来实现动画,通过定时器周期性地更新视图的位置来实现位移动画。
示例代码:
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
CGPoint startPoint = view.center;
CGPoint endPoint = CGPointMake(300, 300);
NSTimeInterval duration = 2.0; // 动画持续时间
NSTimeInterval interval = 0.01; // 定时器触发间隔
__block NSTimeInterval elapsedTime = 0;
// 使用GCD定时器
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timer, ^{
elapsedTime += interval;
if (elapsedTime >= duration) {
dispatch_source_cancel(timer);
view.center = endPoint;
} else {
CGFloat t = elapsedTime / duration;
view.center = CGPointMake(startPoint.x + (endPoint.x - startPoint.x) * t,
startPoint.y + (endPoint.y - startPoint.y) * t);
}
});
dispatch_resume(timer);
方法五:使用CADisplayLink
CADisplayLink
是一个高效的定时器,可以在屏幕刷新时调用指定的方法。通过它可以实现更平滑的动画效果。
示例代码:
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
CGPoint startPoint = view.center;
CGPoint endPoint = CGPointMake(300, 300);
NSTimeInterval duration = 2.0; // 动画持续时间
__block NSTimeInterval startTime = CACurrentMediaTime();
// 使用CADisplayLink
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateAnimation:)];
displayLink.userInfo = @{@"view": view,
@"startPoint": [NSValue valueWithCGPoint:startPoint],
@"endPoint": [NSValue valueWithCGPoint:endPoint],
@"duration": @(duration),
@"startTime": @(startTime)};
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
- (void)updateAnimation:(CADisplayLink *)displayLink {
NSDictionary *userInfo = displayLink.userInfo;
UIView *view = userInfo[@"view"];
CGPoint startPoint = [userInfo[@"startPoint"] CGPointValue];
CGPoint endPoint = [userInfo[@"endPoint"] CGPointValue];
NSTimeInterval duration = [userInfo[@"duration"] doubleValue];
NSTimeInterval startTime = [userInfo[@"startTime"] doubleValue];
NSTimeInterval currentTime = CACurrentMediaTime();
NSTimeInterval elapsedTime = currentTime - startTime;
if (elapsedTime >= duration) {
view.center = endPoint;
[displayLink invalidate];
} else {
CGFloat t = elapsedTime / duration;
view.center = CGPointMake(startPoint.x + (endPoint.x - startPoint.x) * t,
startPoint.y + (endPoint.y - startPoint.y) * t);
}
}
CADisplayLink:
通过屏幕刷新时调用的方法,更新视图的位置实现动画。
这两种方法都提供了对动画过程的精细控制,但CADisplayLink
通常更适合实现平滑动画,因为它与屏幕刷新频率同步。
方法六:手势识别(Gesture Recognizers)
虽然手势识别通常用于用户交互,但也可以用于自动移动视图。通过模拟手势,可以实现视图的动画效果。
示例代码:
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
CGPoint endPoint = CGPointMake(300, 300);
// 创建一个手势识别器
UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
[view addGestureRecognizer:panGesture];
// 模拟手势移动
[UIView animateWithDuration:2.0
animations:^{
view.center = endPoint;
}];
// 手势处理方法
- (void)handlePan:(UIPanGestureRecognizer *)gesture {
// 处理手势逻辑
}
方法七:KVO(Key-Value Observing)结合自定义动画逻辑
通过KVO监控视图的位置变化,并在变化时执行自定义动画逻辑。
示例代码:
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
// 添加KVO监听
[view addObserver:self forKeyPath:@"center" options:NSKeyValueObservingOptionNew context:nil];
// 目标位置
CGPoint endPoint = CGPointMake(300, 300);
// 手动更新视图位置
dispatch_async(dispatch_get_main_queue(), ^{
view.center = endPoint;
});
// KVO回调方法
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context {
if ([keyPath isEqualToString:@"center"]) {
// 自定义动画逻辑
CGPoint newCenter = [change[NSKeyValueChangeNewKey] CGPointValue];
[UIView animateWithDuration:2.0 animations:^{
((UIView *)object).center = newCenter;
}];
}
}
方法八:UIKit Dynamics
UIKit Dynamics提供了一种物理引擎,可以用来实现基于物理的动画效果。
示例代码:
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
// 创建动力学动画器
UIDynamicAnimator *animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
// 创建重力行为
UIGravityBehavior *gravity = [[UIGravityBehavior alloc] initWithItems:@[view]];
// 创建碰撞行为
UICollisionBehavior *collision = [[UICollisionBehavior alloc] initWithItems:@[view]];
collision.translatesReferenceBoundsIntoBoundary = YES;
// 添加行为到动画器
[animator addBehavior:gravity];
[animator addBehavior:collision];
方法九:Core Graphics动画
可以使用Core Graphics绘制视图,然后通过逐帧绘制实现动画效果。
示例代码:
@implementation CustomView
- (void)drawRect:(CGRect)rect {
// 自定义绘制代码
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, [UIColor blueColor].CGColor);
CGContextFillRect(context, rect);
}
@end
CustomView *view = [[CustomView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
[self.view addSubview:view];
CGPoint endPoint = CGPointMake(300, 300);
// 使用定时器逐帧绘制
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(updateView:) userInfo:@{@"view": view, @"endPoint": [NSValue valueWithCGPoint:endPoint]} repeats:YES];
// 更新视图位置
- (void)updateView:(NSTimer *)timer {
NSDictionary *userInfo = timer.userInfo;
CustomView *view = userInfo[@"view"];
CGPoint endPoint = [userInfo[@"endPoint"] CGPointValue];
// 计算新的位置
CGFloat dx = (endPoint.x - view.center.x) * 0.1;
CGFloat dy = (endPoint.y - view.center.y) * 0.1;
view.center = CGPointMake(view.center.x + dx, view.center.y + dy);
// 检查是否到达目标位置
if (fabs(view.center.x - endPoint.x) < 1 && fabs(view.center.y - endPoint.y) < 1) {
[timer invalidate];
}
}
方法十: 使用CGAffineTransform
进行平移
CGAffineTransform
提供了一种矩阵变换的方法,可以用来对视图进行平移、缩放、旋转等操作。通过transform
属性,可以实现位移动画。
示例代码:
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
CGPoint endPoint = CGPointMake(300, 300);
CGPoint translation = CGPointMake(endPoint.x - view.center.x, endPoint.y - view.center.y);
[UIView animateWithDuration:2.0 animations:^{
view.transform = CGAffineTransformTranslate(view.transform, translation.x, translation.y);
}];
方法十一:Layer的position
属性(不使用CABasicAnimation
)
虽然不能使用CABasicAnimation
,但可以直接修改图层的position
属性,并使用定时器或CADisplayLink
来平滑过渡。
示例代码:
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
CGPoint startPoint = view.layer.position;
CGPoint endPoint = CGPointMake(300, 300);
NSTimeInterval duration = 2.0; // 动画持续时间
__block NSTimeInterval startTime = CACurrentMediaTime();
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateLayerPosition:)];
displayLink.userInfo = @{@"layer": view.layer,
@"startPoint": [NSValue valueWithCGPoint:startPoint],
@"endPoint": [NSValue valueWithCGPoint:endPoint],
@"duration": @(duration),
@"startTime": @(startTime)};
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
- (void)updateLayerPosition:(CADisplayLink *)displayLink {
NSDictionary *userInfo = displayLink.userInfo;
CALayer *layer = userInfo[@"layer"];
CGPoint startPoint = [userInfo[@"startPoint"] CGPointValue];
CGPoint endPoint = [userInfo[@"endPoint"] CGPointValue];
NSTimeInterval duration = [userInfo[@"duration"] doubleValue];
NSTimeInterval startTime = [userInfo[@"startTime"] doubleValue];
NSTimeInterval currentTime = CACurrentMediaTime();
NSTimeInterval elapsedTime = currentTime - startTime;
if (elapsedTime >= duration) {
layer.position = endPoint;
[displayLink invalidate];
} else {
CGFloat t = elapsedTime / duration;
layer.position = CGPointMake(startPoint.x + (endPoint.x - startPoint.x) * t,
startPoint.y + (endPoint.y - startPoint.y) * t);
}
}
方法十二:自定义动画引擎
可以实现一个简单的自定义动画引擎,通过插值计算来逐帧更新视图的位置。
示例代码:
@interface CustomAnimator : NSObject
@property (nonatomic, weak) UIView *view;
@property (nonatomic) CGPoint startPoint;
@property (nonatomic) CGPoint endPoint;
@property (nonatomic) NSTimeInterval duration;
@property (nonatomic) NSTimeInterval startTime;
@property (nonatomic, strong) CADisplayLink *displayLink;
- (void)startAnimation;
@end
@implementation CustomAnimator
- (void)startAnimation {
self.startTime = CACurrentMediaTime();
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateAnimation)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)updateAnimation {
NSTimeInterval currentTime = CACurrentMediaTime();
NSTimeInterval elapsedTime = currentTime - self.startTime;
if (elapsedTime >= self.duration) {
self.view.center = self.endPoint;
[self.displayLink invalidate];
} else {
CGFloat t = elapsedTime / self.duration;
self.view.center = CGPointMake(self.startPoint.x + (self.endPoint.x - self.startPoint.x) * t,
self.startPoint.y + (self.endPoint.y - self.startPoint.y) * t);
}
}
@end
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
view.backgroundColor = [UIColor blueColor];
[self.view addSubview:view];
CustomAnimator *animator = [[CustomAnimator alloc] init];
animator.view = view;
animator.startPoint = view.center;
animator.endPoint = CGPointMake(300, 300);
animator.duration = 2.0;
[animator startAnimation];
方法十三: 使用UIScrollView
的contentOffset
属性
通过将目标视图嵌入到一个UIScrollView
中,并调整contentOffset
来实现视图的移动。UIScrollView
的平滑滚动机制可以用来模拟动画效果。
示例代码:
// 创建一个 UIScrollView
UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds];
scrollView.backgroundColor = [UIColor clearColor];
scrollView.contentSize = CGSizeMake(self.view.bounds.size.width * 2, self.view.bounds.size.height);
[self.view addSubview:scrollView];
// 创建要移动的目标视图
UIView *targetView = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];
targetView.backgroundColor = [UIColor blueColor];
[scrollView addSubview:targetView];
// 设置起点和终点
CGPoint startPoint = targetView.frame.origin;
CGPoint endPoint = CGPointMake(300, 100);
// 初始位置(将scrollView的contentOffset设置为起点)
scrollView.contentOffset = startPoint;
// 使用 scrollView 的动画方法模拟移动
[UIView animateWithDuration:2.0 animations:^{
scrollView.contentOffset = endPoint;
}];
分析
-
创建
UIScrollView
:首先创建一个UIScrollView
并设置其contentSize
大于屏幕的大小,以便有足够的空间进行平移。 -
添加目标视图 :将目标视图添加到
UIScrollView
中,并设置其初始位置。 -
设置起点和终点 :通过调整
UIScrollView
的contentOffset
属性来实现视图的移动。 -
动画实现 :使用
UIView
的动画块来平滑地改变contentOffset
,从而实现目标视图的移动。