使用遮罩 show/hide 带有动画的视图
Using mask to show/hide a view with animation
我正在尝试使用遮罩来隐藏和显示视图,但由于某种原因我无法使其正常工作。
我真的很困惑是否应该为蒙版的 frame/bounds/path 设置动画。
这是我的代码,用于显示按钮操作的视图 "GO":
- (IBAction)go:(id)sender
{
[UIView animateWithDuration:3.0 animations:^{
self.testView.layer.mask.bounds = self.testView.layer.bounds;
}];
}
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect rect = CGRectMake(0, 0, self.testView.bounds.size.width, 0);
CGPathRef path = CGPathCreateWithRect(rect, NULL);
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = path;
self.testView.layer.mask = shapeLayer;
self.testView.layer.mask.bounds = rect;
}
谢谢!
您正在更改掩码的 bounds
,而不是 path
。您确实需要更改 mask
的 path
。理论上你可以使用 CABasicAnimation
来做到这一点,但我个人发现在动画路径(尤其是蒙版的路径)时非常不稳定。
如果可以的话,我会完全取消遮罩,只设置 testView
的框架,使其不可见(例如,高度为零),然后使用块为该框架的变化设置动画基于 UIView
animateWithDuration
。 (注意,如果使用自动布局,那么您可以为 setNeedsLayout
更改约束设置动画)。
如果你真的需要使用CAShapeLayer
掩码,你可以尝试CABAsicAnimation
和animateWithKeyPath
掩码path
键上的CAShapeLayer
.
就我个人而言,在为路径的变化设置动画时,我会使用显示 link,例如类似于:
- (IBAction)didTapButton:(UIBarButtonItem *)sender {
[AnimationDisplayLink animateWithDuration:3.0 animationHandler:^(CGFloat percent) {
self.mask.path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, self.testView.bounds.size.width, self.testView.bounds.size.height * percent)].CGPath;
} completionHandler:nil];
}
其中我的AnimationDisplayLink
定义如下:
@interface AnimationDisplayLink : NSObject
@property (nonatomic) CGFloat animationDuration;
@property (nonatomic, copy) void(^animationHandler)(CGFloat percent);
@property (nonatomic, copy) void(^completionHandler)();
@end
@interface AnimationDisplayLink ()
@property (nonatomic) CFAbsoluteTime startTime;
@property (nonatomic, strong) CADisplayLink *displayLink;
@end
@implementation AnimationDisplayLink
+ (instancetype)animateWithDuration:(CGFloat)duration animationHandler:(void (^)(CGFloat percent))animationHandler completionHandler:(void (^)(void))completionHandler {
AnimationDisplayLink *handler = [[self alloc] init];
handler.animationDuration = duration;
handler.animationHandler = animationHandler;
handler.completionHandler = completionHandler;
[handler startAnimation];
return handler;
}
- (void)startAnimation {
self.startTime = CFAbsoluteTimeGetCurrent();
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)stopAnimation {
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink {
CGFloat elapsed = CFAbsoluteTimeGetCurrent() - self.startTime;
CGFloat percent = elapsed / self.animationDuration;
if (percent >= 1.0) {
[self stopAnimation];
if (self.animationHandler) self.animationHandler(1.0);
if (self.completionHandler) self.completionHandler();
} else {
if (self.animationHandler) self.animationHandler(percent);
}
}
@end
Swift Rob 的回答版本
import UIKit
typealias PercentBasedAnimationHandler = ((_ percent: CGFloat) -> Void)
typealias AnimationCompletionHandler = (() -> Void)
class AnimationDisplayLink: NSObject {
var animationDuration: CGFloat = 0.3
var animationHandler: PercentBasedAnimationHandler!
var completionHandler: AnimationCompletionHandler!
var startTime: CFAbsoluteTime!
var displayLink: CADisplayLink!
static func animateWith(duration: CGFloat, animationHanlder: @escaping PercentBasedAnimationHandler, completionHandler: @escaping AnimationCompletionHandler) -> AnimationDisplayLink {
let handler = AnimationDisplayLink()
handler.animationDuration = duration
handler.animationHandler = animationHanlder
handler.completionHandler = completionHandler
handler.startAnimation()
return handler
}
func startAnimation() {
self.startTime = CFAbsoluteTimeGetCurrent()
self.displayLink = CADisplayLink(target: self, selector: #selector(hanldeDisplayLink(displayLink:)))
self.displayLink.add(to: RunLoop.main, forMode: .commonModes)
}
func stopAnimation() {
self.displayLink.invalidate()
self.displayLink = nil
}
@objc func hanldeDisplayLink(displayLink: CADisplayLink) {
let elapsed = CFAbsoluteTimeGetCurrent() - self.startTime
let percent = CGFloat(elapsed) / animationDuration
if percent >= 1.0 {
stopAnimation()
self.animationHandler(1.0)
self.completionHandler()
} else {
self.animationHandler(percent)
}
}
}
我正在尝试使用遮罩来隐藏和显示视图,但由于某种原因我无法使其正常工作。
我真的很困惑是否应该为蒙版的 frame/bounds/path 设置动画。
这是我的代码,用于显示按钮操作的视图 "GO":
- (IBAction)go:(id)sender
{
[UIView animateWithDuration:3.0 animations:^{
self.testView.layer.mask.bounds = self.testView.layer.bounds;
}];
}
- (void)viewDidLoad
{
[super viewDidLoad];
CGRect rect = CGRectMake(0, 0, self.testView.bounds.size.width, 0);
CGPathRef path = CGPathCreateWithRect(rect, NULL);
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = path;
self.testView.layer.mask = shapeLayer;
self.testView.layer.mask.bounds = rect;
}
谢谢!
您正在更改掩码的 bounds
,而不是 path
。您确实需要更改 mask
的 path
。理论上你可以使用 CABasicAnimation
来做到这一点,但我个人发现在动画路径(尤其是蒙版的路径)时非常不稳定。
如果可以的话,我会完全取消遮罩,只设置 testView
的框架,使其不可见(例如,高度为零),然后使用块为该框架的变化设置动画基于 UIView
animateWithDuration
。 (注意,如果使用自动布局,那么您可以为 setNeedsLayout
更改约束设置动画)。
如果你真的需要使用CAShapeLayer
掩码,你可以尝试CABAsicAnimation
和animateWithKeyPath
掩码path
键上的CAShapeLayer
.
就我个人而言,在为路径的变化设置动画时,我会使用显示 link,例如类似于:
- (IBAction)didTapButton:(UIBarButtonItem *)sender {
[AnimationDisplayLink animateWithDuration:3.0 animationHandler:^(CGFloat percent) {
self.mask.path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, self.testView.bounds.size.width, self.testView.bounds.size.height * percent)].CGPath;
} completionHandler:nil];
}
其中我的AnimationDisplayLink
定义如下:
@interface AnimationDisplayLink : NSObject
@property (nonatomic) CGFloat animationDuration;
@property (nonatomic, copy) void(^animationHandler)(CGFloat percent);
@property (nonatomic, copy) void(^completionHandler)();
@end
@interface AnimationDisplayLink ()
@property (nonatomic) CFAbsoluteTime startTime;
@property (nonatomic, strong) CADisplayLink *displayLink;
@end
@implementation AnimationDisplayLink
+ (instancetype)animateWithDuration:(CGFloat)duration animationHandler:(void (^)(CGFloat percent))animationHandler completionHandler:(void (^)(void))completionHandler {
AnimationDisplayLink *handler = [[self alloc] init];
handler.animationDuration = duration;
handler.animationHandler = animationHandler;
handler.completionHandler = completionHandler;
[handler startAnimation];
return handler;
}
- (void)startAnimation {
self.startTime = CFAbsoluteTimeGetCurrent();
self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)stopAnimation {
[self.displayLink invalidate];
self.displayLink = nil;
}
- (void)handleDisplayLink:(CADisplayLink *)displayLink {
CGFloat elapsed = CFAbsoluteTimeGetCurrent() - self.startTime;
CGFloat percent = elapsed / self.animationDuration;
if (percent >= 1.0) {
[self stopAnimation];
if (self.animationHandler) self.animationHandler(1.0);
if (self.completionHandler) self.completionHandler();
} else {
if (self.animationHandler) self.animationHandler(percent);
}
}
@end
Swift Rob 的回答版本
import UIKit
typealias PercentBasedAnimationHandler = ((_ percent: CGFloat) -> Void)
typealias AnimationCompletionHandler = (() -> Void)
class AnimationDisplayLink: NSObject {
var animationDuration: CGFloat = 0.3
var animationHandler: PercentBasedAnimationHandler!
var completionHandler: AnimationCompletionHandler!
var startTime: CFAbsoluteTime!
var displayLink: CADisplayLink!
static func animateWith(duration: CGFloat, animationHanlder: @escaping PercentBasedAnimationHandler, completionHandler: @escaping AnimationCompletionHandler) -> AnimationDisplayLink {
let handler = AnimationDisplayLink()
handler.animationDuration = duration
handler.animationHandler = animationHanlder
handler.completionHandler = completionHandler
handler.startAnimation()
return handler
}
func startAnimation() {
self.startTime = CFAbsoluteTimeGetCurrent()
self.displayLink = CADisplayLink(target: self, selector: #selector(hanldeDisplayLink(displayLink:)))
self.displayLink.add(to: RunLoop.main, forMode: .commonModes)
}
func stopAnimation() {
self.displayLink.invalidate()
self.displayLink = nil
}
@objc func hanldeDisplayLink(displayLink: CADisplayLink) {
let elapsed = CFAbsoluteTimeGetCurrent() - self.startTime
let percent = CGFloat(elapsed) / animationDuration
if percent >= 1.0 {
stopAnimation()
self.animationHandler(1.0)
self.completionHandler()
} else {
self.animationHandler(percent)
}
}
}