IPhone "слайд для разблокировки"
Любые идеи относительно того, как Apple реализовала "слайд для разблокировки" (также, "слайд для выключения" - еще один пример) анимация?
Я подумал о какой-то анимационной маске - но маскировка недоступна в iPhone OS по соображениям производительности.
Есть ли какой-то частный API-эффект (например, SuckEffect), который они могли бы использовать? Эффект прожектора? Что такое Core Animation?
Изменить:
Это определенно не сериал. Я видел примеры редактирования значения plist или чего-то другого и настройки строки на jailbroken iphones.
Ответы
Ответ 1
Это можно легко сделать, используя Core Animation, анимацию слоя маски на слое, отображающем текст.
Попробуйте это в любом обычном UIViewController (вы можете начать с нового проекта Xcode на основе шаблона проекта приложения на основе View) или захватить мой проект 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 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];
}
Изображения, используемые этим кодом:
![Текстовый слой]()
Ответ 2
Еще одно решение, использующее маску слоя, но вместо этого рисует градиент вручную и не требует изображений. Вид - это представление с анимацией, прозрачность - это float от 0 до 1, определяющая количество прозрачности (1 = отсутствие прозрачности, которая бессмысленно), а gradientWidth - желаемая ширина градиента.
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"];
ВЕРСИЯ SWIFT:
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)
}
Ответ 3
Вы можете использовать режим рисования 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, чтобы сделать то же самое.
Ответ 4
Я добавил код, приведенный выше Паскалем как категорию на UILabel, чтобы вы могли анимировать любой 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 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
Ответ 5
не так свежо... но, возможно, это будет полезно
#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];
}
Ответ 6
Благодаря rpetrich для рецепта градиента отсечения. Я новичок iPhone и разработчик Cocoa, поэтому я был очень рад найти его.
Я реализовал достойный вид слайда для отмены UIViewController с использованием метода rpetrich. Вы можете загрузить проект Xcode моей реализации из здесь.
В моей реализации используется повторяющийся NSTimer. Мне не удалось выяснить, как использовать анимацию Core (или Gore), чтобы графический движок iPhone постоянно перемещал выделение. Я думаю, что это можно сделать на OS X с уровнями маски CALayer, но слои маски не поддерживаются на iPhone OS.
Когда я играю с ползунком "Слайд для разблокировки" Apple на главном экране iPhone, я иногда вижу замораживание анимации. Поэтому я думаю, что Apple также может использовать таймер.
Если кто-то может понять, как сделать реализацию с использованием таймера без таймера, используя CA или OpenGL, я бы с удовольствием это увидел.
Спасибо за помощь!
Ответ 7
Я знаю, я немного опоздал с ответом, но у Facebook есть отличная библиотека Shimmer, которая реализует именно этот эффект.
Ответ 8
Я загрузил в GitHub мини-проект, который помогает с анимацией "слайд разблокировать".
https://github.com/GabrielMassana/GM_FSHighlightAnimationAdditions
В проекте есть анимации LTR, RTL, Up to Down и Down to Up, и они основаны на сообщениях:
Паскаль Бурк: fooobar.com/questions/78870/...
cberkley: fooobar.com/questions/78870/...
Приветствия
Ответ 9
Я взял лучшее из вышеперечисленных решений и создал аккуратный метод, который делает все для вас:
- (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];
}
Ответ 10
Во-первых, ОГРОМНОЕ спасибо марсио за его решение. Это работало почти отлично, сэкономило много усилий и сделало огромный всплеск в моем приложении. Мой босс любил это. Я должен тебе пиво. Или несколько.
Одна небольшая коррекция только для iPhone 4. Я имею в виду само оборудование, а не только iOS 4. Они изменили системный шрифт на iPhone 4 от Helvetica (iPhone 3Gs и ниже) до Helvetic Neue. Это заставило перевод, который вы делаете, от персонажа до глифов, чтобы отключить ровно 4 пятна. Например, строка "fg" будет выглядеть как "bc". Я исправил это, явно установив шрифт на "Helvetica", а не используя "systemFontofSize". Теперь это работает как шарм.
Снова... СПАСИБО!
Ответ 11
Может быть, это просто визуализированная анимация - вы знаете, серия неподвижных игр играла один за другим. Не обязательно динамический эффект.
Обновление: неважно, видео, опубликованное DrJokepu, подтвердило его динамическое генерирование.
Ответ 12
- Вверх: UILabel с непрозрачным фоном и прозрачным текстом
- Открытый текст отображается в drawRect: func через сложный процесс маскировки
- Средний: рабочий просмотр, который выполняет повторяющуюся анимацию, перемещающую изображение за верхней меткой.
- Внизу: UIView, в который вы добавляете средний и верхний объекты в этом порядке. Может быть любой цвет, который вы хотите, чтобы текст был
Пример можно увидеть здесь https://github.com/jhurray/AnimatedLabelExample