IPhone“幻灯片解锁”动画

关于苹果如何实现“幻灯片解锁”(还有,“幻灯片解锁”是另一个相同的例子)动画,有什么想法吗?

我考虑过使用某种动画蒙版,但由于性能原因,在 iPhone 操作系统上无法使用蒙版。

是否有一些私有的 API 效应(如吸效应) ,他们可能已经使用?聚光灯效果?核心动画什么的?

编辑: 绝对不是一系列的剧照。我曾经看到过在破解的 iphone 上编辑 plist 值或者其他东西并定制字符串的例子。

32914 次浏览

也许这只是一个渲染出来的动画-你知道,一系列的剧照一个接一个地播放。不一定是动态效应。

更新: 没关系,DrJokepu 发布的视频证明了它是动态生成的。

可以使用 kCGTextClip绘图模式设置剪切路径,然后用渐变填充。

// Get Context
CGContextRef context = UIGraphicsGetCurrentContext();
// Set Font
CGContextSelectFont(context, "Helvetica", 24.0, kCGEncodingMacRoman);
// Set Text Matrix
CGAffineTransform xform = CGAffineTransformMake(1.0,  0.0,
0.0, -1.0,
0.0,  0.0);
CGContextSetTextMatrix(context, xform);
// Set Drawing Mode to set clipping path
CGContextSetTextDrawingMode (context, kCGTextClip);
// Draw Text
CGContextShowTextAtPoint (context, 0, 20, "Gradient", strlen("Gradient"));
// Calculate Text width
CGPoint textEnd = CGContextGetTextPosition(context);
// Generate Gradient locations & colors
size_t num_locations = 3;
CGFloat locations[3] = { 0.3, 0.5, 0.6 };
CGFloat components[12] = {
1.0, 1.0, 1.0, 0.5,
1.0, 1.0, 1.0, 1.0,
1.0, 1.0, 1.0, 0.5,
};
// Load Colorspace
CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
// Create Gradient
CGGradientRef gradient = CGGradientCreateWithColorComponents (colorspace, components,
locations, num_locations);
// Draw Gradient (using clipping path
CGContextDrawLinearGradient (context, gradient, rect.origin, textEnd, 0);
// Cleanup (exercise for reader)

设置一个 NSTimer 并在位置中改变值,或者使用 CoreAnimation 执行相同的操作。

多亏了 rpetrich 的剪切渐变配方。我是一个新手 iPhone 和可可开发者,所以我真的很高兴找到它。

我使用 rpetrich 的方法实现了一个外观不错的 取消幻灯片 UIViewController。

我的实现使用重复的 NSTimer。我无法弄清楚如何使用 Core (或者 Gore) Animation 使 iPhone 的图形引擎不断移动高亮。我认为可以在 OS X 上使用 CALayer 掩码层,但是 iPhone OS 不支持掩码层。

当我在 iPhone 的主屏幕上玩苹果的“幻灯片解锁”滑块时,偶尔会看到动画定格。所以我认为苹果可能也在使用定时器。

如果有人知道如何使用 CA 或 OpenGL 实现非定时器,我很想看看。

谢谢你的帮助!

不是很新鲜... 但也许会有用

#define MM_TEXT_TO_DISPLAY          @"default"


#define MM_FONT             [UIFont systemFontOfSize:MM_FONT_SIZE]
#define MM_FONT_SIZE            25
#define MM_FONT_COLOR           [[UIColor darkGrayColor] colorWithAlphaComponent:0.75f];


#define MM_SHADOW_ENABLED           NO
#define MM_SHADOW_COLOR         [UIColor grayColor]
#define MM_SHADOW_OFFSET            CGSizeMake(-1,-1)




#define MM_CONTENT_EDGE_INSETS_TOP      0
#define MM_CONTENT_EDGE_INSETS_LEFT     10
#define MM_CONTENT_EDGE_INSETS_BOTTON   0
#define MM_CONTENT_EDGE_INSETS_RIGHT    10
#define MM_CONTENT_EDGE_INSETS          UIEdgeInsetsMake(MM_CONTENT_EDGE_INSETS_TOP, MM_CONTENT_EDGE_INSETS_LEFT, MM_CONTENT_EDGE_INSETS_BOTTON, MM_CONTENT_EDGE_INSETS_RIGHT)


#define MM_TEXT_ALIGNMENT           UITextAlignmentCenter
#define MM_BACKGROUND_COLOR         [UIColor clearColor]


#define MM_TIMER_INTERVAL           0.05f
#define MM_HORIZONTAL_SPAN          5




@interface MMAnimatedGradientLabel : UILabel {


NSString *textToDisplay;
int text_length;


CGGradientRef gradient;


int current_position_x;
NSTimer *timer;


CGPoint alignment;


CGGlyph *_glyphs;
}


- (id)initWithString:(NSString *)_string;


- (void)startAnimation;
- (void)toggle;
- (BOOL)isAnimating;


@end


#define RGB_COMPONENTS(r, g, b, a)  (r) / 255.0f, (g) / 255.0f, (b) / 255.0f, (a)


@interface MMAnimatedGradientLabel (Private)
- (CGRect)calculateFrame;
@end




@implementation MMAnimatedGradientLabel


// Missing in standard headers.
extern void CGFontGetGlyphsForUnichars(CGFontRef, const UniChar[], const CGGlyph[], size_t);


- (id)init {
textToDisplay = MM_TEXT_TO_DISPLAY;
return [self initWithFrame:[self calculateFrame]];
}


- (id)initWithString:(NSString *)_string {
textToDisplay = _string;
return [self initWithFrame:[self calculateFrame]];
}


-(id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {


// set default values
//
self.textAlignment      = MM_TEXT_ALIGNMENT;
self.backgroundColor    = MM_BACKGROUND_COLOR;
self.font               = MM_FONT;
self.text               = textToDisplay;
self.textColor          = MM_FONT_COLOR;


if (MM_SHADOW_ENABLED) {
self.shadowColor        = MM_SHADOW_COLOR;
self.shadowOffset       = MM_SHADOW_OFFSET;
}


text_length = -1;


CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGFloat colors[] =
{
RGB_COMPONENTS(255.0, 255.0, 255.0, 0.00),
//          RGB_COMPONENTS(255.0, 255.0, 255.0, 0.15),
RGB_COMPONENTS(255.0, 255.0, 255.0, 0.95),
//          RGB_COMPONENTS(255.0, 255.0, 255.0, 0.15),
RGB_COMPONENTS(255.0, 255.0, 255.0, 0.00)
};


gradient = CGGradientCreateWithColorComponents(rgb, colors, NULL, sizeof(colors)/(sizeof(colors[0])*4));
CGColorSpaceRelease(rgb);


current_position_x = -(frame.size.width/2);// - MM_CONTENT_EDGE_INSETS.left - MM_CONTENT_EDGE_INSETS.right);
}


return self;
}


- (CGRect)calculateFrame {
CGSize size = [textToDisplay sizeWithFont:MM_FONT];
NSLog(@"size: %f, %f", size.width, size.height);
return CGRectMake(0, 0, size.width + MM_CONTENT_EDGE_INSETS.left + MM_CONTENT_EDGE_INSETS.right, size.height + MM_CONTENT_EDGE_INSETS.top + MM_CONTENT_EDGE_INSETS.bottom);
}


- (void)tick:(NSTimer*)theTimer {
if (current_position_x < self.frame.size.width)
current_position_x = current_position_x + MM_HORIZONTAL_SPAN;
else
current_position_x = -(self.frame.size.width/2); // - MM_CONTENT_EDGE_INSETS.left - MM_CONTENT_EDGE_INSETS.right);


[self setNeedsDisplay];
}


- (void)startAnimation {
timer = [[NSTimer alloc] initWithFireDate:[NSDate date]
interval:MM_TIMER_INTERVAL
target:self
selector:@selector(tick:)
userInfo:nil
repeats:YES];


[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}


- (void)toggle {


if (!timer) {
timer = [[NSTimer alloc] initWithFireDate:[NSDate date]
interval:MM_TIMER_INTERVAL
target:self
selector:@selector(tick:)
userInfo:nil
repeats:YES];


[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
} else {
[timer invalidate];
[timer release];
timer = nil;


current_position_x = -(self.frame.size.width/2);
[self setNeedsDisplay];
}
}


- (BOOL)isAnimating {


if (timer)
return YES;
else
return NO;
}


- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();


// Get drawing font.
CGFontRef font = CGFontCreateWithFontName((CFStringRef)[[self font] fontName]);
CGContextSetFont(ctx, font);
CGContextSetFontSize(ctx, [[self font] pointSize]);


// Calculate text drawing point only first time
//
if (text_length == -1) {


// Transform text characters to unicode glyphs.
text_length = [[self text] length];
unichar chars[text_length];
[[self text] getCharacters:chars range:NSMakeRange(0, text_length)];


_glyphs = malloc(sizeof(CGGlyph) * text_length);
for (int i=0; i<text_length;i ++)
_glyphs[i] = chars[i] - 29;


// Measure text dimensions.
CGContextSetTextDrawingMode(ctx, kCGTextInvisible);
CGContextSetTextPosition(ctx, 0, 0);
CGContextShowGlyphs(ctx, _glyphs, text_length);
CGPoint textEnd = CGContextGetTextPosition(ctx);


// Calculate text drawing point.
CGPoint anchor = CGPointMake(textEnd.x * (-0.5), [[self font] pointSize] * (-0.25));
CGPoint p = CGPointApplyAffineTransform(anchor, CGAffineTransformMake(1, 0, 0, -1, 0, 1));


if ([self textAlignment] == UITextAlignmentCenter)
alignment.x = [self bounds].size.width * 0.5 + p.x;
else if ([self textAlignment] == UITextAlignmentLeft)
alignment.x = 0;
else
alignment.x = [self bounds].size.width - textEnd.x;


alignment.y = [self bounds].size.height * 0.5 + p.y;
}


// Flip back mirrored text.
CGContextSetTextMatrix(ctx, CGAffineTransformMakeScale(1, -1));


// Draw shadow.
CGContextSaveGState(ctx);
CGContextSetTextDrawingMode(ctx, kCGTextFill);
CGContextSetFillColorWithColor(ctx, [[self textColor] CGColor]);
CGContextSetShadowWithColor(ctx, [self shadowOffset], 0, [[self shadowColor] CGColor]);
CGContextShowGlyphsAtPoint(ctx, alignment.x, alignment.y, _glyphs, text_length);
CGContextRestoreGState(ctx);


// Draw text clipping path.
CGContextSetTextDrawingMode(ctx, kCGTextClip);
CGContextShowGlyphsAtPoint(ctx, alignment.x, alignment.y, _glyphs, text_length);


// Restore text mirroring.
CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);


if ([self isAnimating]) {
// Fill text clipping path with gradient.
CGPoint start = CGPointMake(rect.origin.x + current_position_x, rect.origin.y);
CGPoint end = CGPointMake(rect.size.width/3*2 + current_position_x, rect.origin.y);


CGContextDrawLinearGradient(ctx, gradient, start, end, 0);
}
}




- (void) dealloc {
free(_glyphs);
[timer invalidate];
[timer release];


CGGradientRelease(gradient);
[super dealloc];
}

它可以很容易地做到使用核心动画,动画蒙版层的层显示文字。

在任何普通的 UIViewController 中都可以尝试这样做(您可以从基于 基于视图的应用程序项目模板的新 Xcode 项目开始) ,或者抓取我的 Xcode 项目 给你:

请注意,CALayer.mask属性仅在 iPhone OS 3.0及更高版本中可用。

- (void)viewDidLoad
{
self.view.layer.backgroundColor = [[UIColor blackColor] CGColor];


UIImage *textImage = [UIImage imageNamed:@"SlideToUnlock.png"];
CGFloat textWidth = textImage.size.width;
CGFloat textHeight = textImage.size.height;


CALayer *textLayer = [CALayer layer];
textLayer.contents = (id)[textImage CGImage];
textLayer.frame = CGRectMake(10.0f, 215.0f, textWidth, textHeight);


CALayer *maskLayer = [CALayer layer];


// Mask image ends with 0.15 opacity on both sides. Set the background color of the layer
// to the same value so the layer can extend the mask image.
maskLayer.backgroundColor = [[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.15f] CGColor];
maskLayer.contents = (id)[[UIImage imageNamed:@"Mask.png"] CGImage];


// Center the mask image on twice the width of the text layer, so it starts to the left
// of the text layer and moves to its right when we translate it by width.
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.frame = CGRectMake(-textWidth, 0.0f, textWidth * 2, textHeight);


// Animate the mask layer's horizontal position
CABasicAnimation *maskAnim = [CABasicAnimation animationWithKeyPath:@"position.x"];
maskAnim.byValue = [NSNumber numberWithFloat:textWidth];
maskAnim.repeatCount = HUGE_VALF;
maskAnim.duration = 1.0f;
[maskLayer addAnimation:maskAnim forKey:@"slideAnim"];


textLayer.mask = maskLayer;
[self.view.layer addSublayer:textLayer];


[super viewDidLoad];
}

该代码使用的图像如下:

Mask Layer Text Layer

首先,非常感谢 Marcio 的解决方案。这个工作几乎完美,节省了我几个小时的努力,并在我的应用程序中引起了巨大的轰动。我老板很喜欢。我欠你啤酒。或者几个。

只对 iPhone4做了一个小小的修正。我指的是硬件本身,而不仅仅是 iOS4。他们将 iPhone4的系统字体从 Helvetica (iPhone3Gs 及以下版本)改为 HelveticNeue。这导致你从字符到象形文字的翻译偏差了整整4个点。例如,字符串“ fg”将显示为“ bc”。我通过显式地将字体设置为“ Helvetica”而不是使用“ systemFontofSize”来修正这个问题。现在它像魔法一样起作用了。

再说一次... 谢谢!

我在 UILabel 上添加了 Pascal 提供的代码作为一个类别,因此您可以以这种方式为任何 UILabel 制作动画。这是密码。一些参数可能需要更改为您的背景颜色等。它使用了帕斯卡在他的答案中嵌入的相同的面具图像。

//UILabel+FSHighlightAnimationAdditions.m
#import "UILabel+FSHighlightAnimationAdditions.h"
#import <UIKit/UIKit.h>
#import <QuartzCore/QuartzCore.h>


@implementation UILabel (FSHighlightAnimationAdditions)


- (void)setTextWithChangeAnimation:(NSString*)text
{
NSLog(@"value changing");
self.text = text;
CALayer *maskLayer = [CALayer layer];


// Mask image ends with 0.15 opacity on both sides. Set the background color of the layer
// to the same value so the layer can extend the mask image.
maskLayer.backgroundColor = [[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.15f] CGColor];
maskLayer.contents = (id)[[UIImage imageNamed:@"Mask.png"] CGImage];


// Center the mask image on twice the width of the text layer, so it starts to the left
// of the text layer and moves to its right when we translate it by width.
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.frame = CGRectMake(self.frame.size.width * -1, 0.0f, self.frame.size.width * 2, self.frame.size.height);


// Animate the mask layer's horizontal position
CABasicAnimation *maskAnim = [CABasicAnimation animationWithKeyPath:@"position.x"];
maskAnim.byValue = [NSNumber numberWithFloat:self.frame.size.width];
maskAnim.repeatCount = 1e100f;
maskAnim.duration = 2.0f;
[maskLayer addAnimation:maskAnim forKey:@"slideAnim"];


self.layer.mask = maskLayer;
}


@end


//UILabel+FSHighlightAnimationAdditions.h
#import <Foundation/Foundation.h>
@interface UILabel (FSHighlightAnimationAdditions)


- (void)setTextWithChangeAnimation:(NSString*)text;


@end

另一个解决方案使用图层蒙版,但是代替手工绘制渐变,不需要图像。视图是带有动画的视图,透明度是从0到1的浮点数,定义透明度的大小(1 = 没有透明度,这是毫无意义的) ,渐变宽度是期望的渐变宽度。

CAGradientLayer *gradientMask = [CAGradientLayer layer];
gradientMask.frame = view.bounds;
CGFloat gradientSize = gradientWidth / view.frame.size.width;
UIColor *gradient = [UIColor colorWithWhite:1.0f alpha:transparency];
NSArray *startLocations = @[[NSNumber numberWithFloat:0.0f], [NSNumber numberWithFloat:(gradientSize / 2)], [NSNumber numberWithFloat:gradientSize]];
NSArray *endLocations = @[[NSNumber numberWithFloat:(1.0f - gradientSize)], [NSNumber numberWithFloat:(1.0f -(gradientSize / 2))], [NSNumber numberWithFloat:1.0f]];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"locations"];


gradientMask.colors = @[(id)gradient.CGColor, (id)[UIColor whiteColor].CGColor, (id)gradient.CGColor];
gradientMask.locations = startLocations;
gradientMask.startPoint = CGPointMake(0 - (gradientSize * 2), .5);
gradientMask.endPoint = CGPointMake(1 + gradientSize, .5);


view.layer.mask = gradientMask;


animation.fromValue = startLocations;
animation.toValue = endLocations;
animation.repeatCount = HUGE_VALF;
animation.duration  = 3.0f;


[gradientMask addAnimation:animation forKey:@"animateGradient"];

快速翻译:

let transparency:CGFloat = 0.5
let gradientWidth: CGFloat = 40


let gradientMask = CAGradientLayer()
gradientMask.frame = swipeView.bounds
let gradientSize = gradientWidth/swipeView.frame.size.width
let gradient = UIColor(white: 1, alpha: transparency)
let startLocations = [0, gradientSize/2, gradientSize]
let endLocations = [(1 - gradientSize), (1 - gradientSize/2), 1]
let animation = CABasicAnimation(keyPath: "locations")


gradientMask.colors = [gradient.CGColor, UIColor.whiteColor().CGColor, gradient.CGColor]
gradientMask.locations = startLocations
gradientMask.startPoint = CGPointMake(0 - (gradientSize*2), 0.5)
gradientMask.endPoint = CGPointMake(1 + gradientSize, 0.5)


swipeView.layer.mask = gradientMask


animation.fromValue = startLocations
animation.toValue = endLocations
animation.repeatCount = HUGE
animation.duration = 3


gradientMask.addAnimation(animation, forKey: "animateGradient")

Swift 3

fileprivate func addGradientMaskToView(view:UIView, transparency:CGFloat = 0.5, gradientWidth:CGFloat = 40.0) {
let gradientMask = CAGradientLayer()
gradientMask.frame = view.bounds
let gradientSize = gradientWidth/view.frame.size.width
let gradientColor = UIColor(white: 1, alpha: transparency)
let startLocations = [0, gradientSize/2, gradientSize]
let endLocations = [(1 - gradientSize), (1 - gradientSize/2), 1]
let animation = CABasicAnimation(keyPath: "locations")


gradientMask.colors = [gradientColor.cgColor, UIColor.white.cgColor, gradientColor.cgColor]
gradientMask.locations = startLocations as [NSNumber]?
gradientMask.startPoint = CGPoint(x:0 - (gradientSize * 2), y: 0.5)
gradientMask.endPoint = CGPoint(x:1 + gradientSize, y: 0.5)


view.layer.mask = gradientMask


animation.fromValue = startLocations
animation.toValue = endLocations
animation.repeatCount = HUGE
animation.duration = 3


gradientMask.add(animation, forKey: nil)
}

我上传到 GitHub 的一个迷你项目谁帮助“幻灯片解锁”动画。

Https://github.com/gabrielmassana/gm_fshighlightanimationadditions

这个项目有 LTR,RTL,Up to Down 和 Down to Up 动画,它是基于文章:

帕斯卡 · 布尔克: https://stackoverflow.com/a/2778232/1381708

https://stackoverflow.com/a/5710097/1381708

干杯

  • 顶部: 带有不透明背景和清晰文本的 UILabel
    • Clear 文本通过复杂的屏蔽过程在 draRect: func 中呈现
  • 中间: 工人视图,执行一个重复的动画移动图像后面的顶部标签
  • 底部: 按照这个顺序添加中间和顶部子视图的 UIView。可以是任意颜色的文本

这里可以看到一个例子 https://github.com/jhurray/AnimatedLabelExample

我知道,我的回答有点晚了,但是 Facebook 有很棒的 淳美库来实现这个效果。

我从上面的解决方案中选取了最好的,并创建了一个简洁的方法,这个方法可以帮助你:

- (void)createSlideToUnlockViewWithText:(NSString *)text
{
UILabel *label = [[UILabel alloc] init];
label.text = text;
[label sizeToFit];
label.textColor = [UIColor whiteColor];


//Create an image from the label
UIGraphicsBeginImageContextWithOptions(label.bounds.size, NO, 0.0);
[[label layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage *textImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();


CGFloat textWidth = textImage.size.width;
CGFloat textHeight = textImage.size.height;


CALayer *textLayer = [CALayer layer];
textLayer.contents = (id)[textImage CGImage];
textLayer.frame = CGRectMake(self.view.frame.size.width / 2 - textWidth / 2, self.view.frame.size.height / 2 - textHeight / 2, textWidth, textHeight);


UIImage *maskImage = [UIImage imageNamed:@"Mask.png"];
CALayer *maskLayer = [CALayer layer];
maskLayer.backgroundColor = [[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.15] CGColor];
maskLayer.contents = (id)maskImage.CGImage;
maskLayer.contentsGravity = kCAGravityCenter;
maskLayer.frame = CGRectMake(-textWidth - maskImage.size.width, 0.0, (textWidth * 2) + maskImage.size.width, textHeight);


CABasicAnimation *maskAnimation = [CABasicAnimation animationWithKeyPath:@"position.x"];
maskAnimation.byValue = [NSNumber numberWithFloat:textWidth + maskImage.size.width];
maskAnimation.repeatCount = HUGE_VALF;
maskAnimation.duration = 2.0;
maskAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[maskLayer addAnimation:maskAnimation forKey:@"slideAnimation"];


textLayer.mask = maskLayer;
self.slideToUnlockLayer = textLayer;
[self.view.layer addSublayer:self.slideToUnlockLayer];
}

这是一个 SwiftUI 版本:

public struct Shimmer: AnimatableModifier {


private let gradient: Gradient


@State private var position: CGFloat = 0


public var animatableData: CGFloat {
get { position }
set { position = newValue }
}


private let animation = Animation
.linear(duration: 2)
.delay(1)
.repeatForever(autoreverses: false)


init(sideColor: Color = Color(white: 0.25), middleColor: Color = .white) {
gradient = Gradient(colors: [sideColor, middleColor, sideColor])
}


public func body(content: Content) -> some View {
LinearGradient(
gradient: gradient,
startPoint: .init(x: position - 0.2 * (1 - position), y: 0.5),
endPoint: .init(x: position + 0.2 * position, y: 0.5)
)
.mask(content)
.onAppear {
withAnimation(animation) {
position = 1
}
}
}
}

像这样使用它:

Text("slide to unlock")
.modifier(Shimmer())