web-dev-qa-db-ja.com

iPhone「スライドしてロック解除」アニメーション

Appleが「ロックを解除するスライド」(また、「電源を切るスライド」も同じ例です)アニメーションを実装する方法に関するアイデアはありますか?

何らかのアニメーションマスクについて考えましたが、パフォーマンス上の理由から、iPhone OSではマスクを使用できません。

彼らが使用した可能性のあるプライベートAPI効果(SuckEffectなど)はありますか?スポットライト型の効果?コアアニメーションのことですか?

編集:それは間違いなく一連の静止画ではありません。 plistの値などを編集し、ジェイルブレイクされたiPhoneで文字列をカスタマイズする例を見てきました。

72
Russ

Core Animationを使用して、テキストを表示するレイヤー上のマスクレイヤーをアニメーション化することで簡単に実行できます。

任意のプレーンな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 LayerText Layer

70
Pascal Bourque

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を使用して同じことを行います。

13
rpetrich

この方法で任意のUILabelをアニメーション化できるように、上記のPascalが提供するコードをUILabelのカテゴリとして追加しました。コードは次のとおりです。背景色などのためにいくつかのパラメータを変更する必要があるかもしれません。Pascalが答えに埋め込んだのと同じマスク画像を使用します。

//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
12
cberkley

それほど新鮮ではありません...しかし、多分それは役に立つでしょう

#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];
}
5
marcio

クリッピンググラデーションレシピのrpetrichに感謝します。私は初心者のiPhoneおよびCocoa開発者なので、見つけて本当にうれしかったです。

Rpetrichのメソッドを使用して、まともなSlide to CancelUIViewControllerを実装しました。私の実装のXcodeプロジェクトは here からダウンロードできます。

私の実装では、繰り返しNSTimerを使用しています。 Core(またはGore)Animationを使用して、iPhoneのグラフィックエンジンが強調表示を連続的に移動させる方法を理解できませんでした。これは、CALayerマスクレイヤーを使用してOS Xで実行できますが、マスクレイヤーはiPhone OSではサポートされていません。

IPhoneのホーム画面でAppleの「Slide to Unlock」スライダーを操作すると、アニメーションがフリーズすることがあります。だからAppleもタイマーを使っているかもしれない。

誰かがCAまたはOpenGLを使用して非タイマーベースの実装を行う方法を理解できるなら、私はそれを見たいです。

助けてくれてありがとう!

4

私は答えに少し遅れていますが、Facebookには素晴らしいライブラリがあります Shimmer それはまさにその効果を実装しています。

4
Maxim Pavlov

上記のソリューションからベストを取り、あなたのためにすべてを行うきちんとした方法を作成しました:

- (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];
}
2
Nico S.

「スライドしてロックを解除する」アニメーションを支援するミニプロジェクトをGitHubにアップロードしました。

https://github.com/GabrielMassana/GM_FSHighlightAnimationAdditions

プロジェクトにはLTR、RTL、Up to DownおよびDown to Upアニメーションがあり、投稿に基づいています:

パスカルブルク: https://stackoverflow.com/a/2778232/1381708

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

乾杯

1
Gabriel.Massana

最初に、彼の解決策についてmarcioに感謝します。これはほぼ完璧に機能し、何時間もの労力を節約し、アプリに大きな衝撃を与えました。私の上司はそれを愛していました。私はあなたにビールを借りています。またはいくつか。

IPhone 4専用の小さな修正。 iOS 4だけでなく、ハードウェア自体を意味します。iPhone4のシステムフォントをHelvetica(iPhone 3G以下)からHelvetic Neueに変更しました。これにより、文字からグリフへの変換が正確に4スポットずれるようになりました。たとえば、文字列「fg」は「bc」と表示されます。 「systemFontofSize」を使用するのではなく、フォントを「Helvetica」に明示的に設定することでこれを修正しました。今では魅力のように機能します。

再びありがとう!

1
Dave Klotz
  • 上:背景が不透明でクリアテキストのUILabel
    • クリアテキストはdrawRectでレンダリングされます:複雑なマスキングプロセスによるfunc
  • 中央:イメージをトップラベルの後ろに移動するアニメーションを繰り返し実行しているワーカービュー
  • 下部:中間および上部のサブビューをこの順序で追加するUIView。あなたがテキストにしたい任意の色にすることができます

ここに例を見ることができます https://github.com/jhurray/AnimatedLabelExample

0
jhurray

たぶん、それは単にレンダリングされたアニメーションです-あなたが知っている、一連の静止画が次々に再生されました。必ずしも動的な効果ではありません。

更新:気にしないで、DrJokepuが投稿したビデオは、動的に生成されたことを証明しました。

0
bhollis