iPhone “slide to unlock” animation

前端 未结 13 699
感情败类
感情败类 2020-11-28 00:49

Any ideas as to how Apple implemented the \"slide to unlock\" (also, \"slide to power off\" is another identical example) animation?

I thought about some sort of ani

相关标签:
13条回答
  • 2020-11-28 01:12

    First, a HUGE thank you to marcio for his solution. This worked almost perfectly, saved me hours of effort, and made a huge splash in my app. My boss loved it. I owe you beer. Or several.

    One small correction for iPhone 4 only. I mean the hardware itself, not just iOS 4. They changed the system font on the iPhone 4 from Helvetica (iPhone 3Gs and below) to Helvetic Neue. This caused the translation you're doing from character to glyphs to be off by exactly 4 spots. For example the string "fg" would appear as "bc". I fixed this by explicitly setting the font to "Helvetica" rather than using "systemFontofSize". Now it works like a charm.

    Again...THANK YOU!

    0 讨论(0)
  • 2020-11-28 01:14

    Here's a SwiftUI version:

    struct Shimmer: AnimatableModifier {
    
        private let gradient: Gradient
    
        init(sideColor: Color = Color(white: 0.25), middleColor: Color = .white) {
            gradient = Gradient(colors: [sideColor, middleColor, sideColor])
        }
    
        @State private var position: CGFloat = 0
        var animatableData: CGFloat {
            get { position }
            set { position = newValue }
        }
    
        func body(content: Content) -> some View {
            content
                .overlay(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
                                    .linear(duration: 2)
                                    .delay(1)
                                    .repeatForever(autoreverses: false)) {
                        position = 1
                    }
                }
        }
    }
    

    Use it like this:

    Text("slide to unlock")
        .modifier(Shimmer())
    
    0 讨论(0)
  • 2020-11-28 01:17

    It can be easilly done by using Core Animation, animating a mask layer on the layer displaying the text.

    Try this in any plain UIViewController (you can start with a new Xcode project based on the View-based application project template), or grab my Xcode project here:

    Note that the CALayer.mask property is only available in iPhone OS 3.0 and later.

    - (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];
    }
    

    The images used by this code are:

    0 讨论(0)
  • 2020-11-28 01:18

    Yet another solution using a layer mask, but instead draws the gradient by hand and does not require images. View is the view with the animation, transparency is a float from 0 - 1 defining the amount of transparency (1 = no transparency which is pointless), and gradientWidth is the desired width of the gradient.

    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 VERSION:

    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)
    }
    
    0 讨论(0)
  • 2020-11-28 01:19

    You can use the kCGTextClip drawing mode to set the clipping path and then fill with a gradient.

    // 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)
    

    Setup an NSTimer and vary the values in locations, or use CoreAnimation to do the same.

    0 讨论(0)
  • 2020-11-28 01:26

    I added the code provided above by Pascal as a category on UILabel so you can animate any UILabel in this fashion. Here's the code. Some params might need to be changed for your background colors, etc. It uses the same mask image that Pascal has embedded in his answer.

    //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 讨论(0)
提交回复
热议问题