CoreAnimationを使用したトランジション:画像の外観のアニメーション化

最近、銀行のアプリケーションの興味深い概念に出会いました。 興味深いのは、どの銀行のモバイルアプリケーションよりもはるかに便利に見えるだけでなく、その素晴らしいアニメーションも興味深いことです。 とても気に入ったので、すぐにどこかに適用することにしました。 特に、ユーザーの写真と彼の地図を管理するためのアイコンの画面上の外観のアニメーションは非常に興味深いように思えました。



ここです











要するに、それが達成することに決めたものです(遅いmoで!)。



ご覧のとおり、アニメーションは2つの部分で構成されています。フレーミングサークルの描画と画像の拡大です。 画像自体はアニメーション中にサイズを変更せず、その可視部分のみが変更されることに注意してください。つまり、この場合、マスクがアニメーション化され、画像が閉じられます。 さらに、これらの2つのアニメーションは同時に開始されません。2番目の部分はマスクを拡大し、円が半分描画されるまで待機してから、画像を表示します。



最初に、Xcodeでプロジェクトを作成するための段階的な指示に時間を無駄にしないために、すでにプロジェクトを作成しており、その構造に多少なりとも慣れていると仮定します。 第二に、以下に示すコードは、UIViewControllerまたはUIViewのどこに配置するかにあまり依存しないため、アニメーションを実行する場所について創造性を発揮できるスペースがあります。 必要な主なものは2つの@propertyです。



@property (nonatomic, weak) IBOutlet UIView *viewToDrawOn; @property (nonatomic, weak) IBOutlet UIView *imageToReveal;
      
      





...ここで、imageToRevealはviewToDrawOnのサブビューです。 英語に精通している場合、viewToDrawOnは描画するUIViewオブジェクトであり、imageToRevealはマスクで開くUIViewオブジェクト(またはUIImageView)であると既に推測しているはずです。 ここでは、どちらもIBOutletキーワードで宣言されています。私は怠け者であり、これらのオブジェクトの作成方法を説明しないためです-Interface Builderビジュアルエディターで直接ストーリーボードに追加しました。 同じ理由で、両方とも弱いと宣言されています-UIViewオブジェクトへの強力なリンクは必要ありません。UIViewオブジェクトは、いずれにせよ上位のUIViewの階層に追加され、これらのオブジェクトは削除されません。 このタンデムで誰がプレビューであるかについての私の言葉では不十分な場合、ここに写真があります。



さらに、ストーリーボードでimageToRevealを非表示にしました(チェックマークは非表示になっています)。 コードでこれを行うことは可能ですが、ビジュアルエディターで可能な限り最大限のカスタマイズを適用し、ソースコードをできるだけ読みやすくすることを好みました。



最後に、ソースコードに移動します。 CABasicAnimationを使用します。 それ(彼?)を使用すると、CALayer型のオブジェクトの一部のプロパティの変化をアニメーション化できます。これは、フレーミングサークルと画像の表示を制限するマスクがCAShapeLayer型のオブジェクトであるため、非常に有益です。 円から始めましょう。



フレーミングサークルを描く



まず、CAShapeLayerオブジェクトを作成します。これはUIViewのレイヤーの1つであり、円を描くグラフのようなものを追加します。 そして、子供の色付けのように、このグラフの形と輪郭に色を付けます。



 //    —  CAShapeLayer *circle = [CAShapeLayer layer]; //      UIView CGFloat radius = self.viewToDrawOn.frame.size.width / 2.0f; circle.position = CGPointZero; circle.path = [UIBezierPath bezierPathWithRoundedRect:self.viewToDrawOn.bounds cornerRadius:radius].CGPath; // ,        circle.fillColor = [UIColor clearColor].CGColor; //  —     UIColor        Hex,       circle.strokeColor = [UIColor colorWithHex:@"ffd800"].CGColor; circle.lineWidth = 1.0f; [self.viewToDrawOn.layer addSublayer:circle];
      
      





アニメーションに直接アクセスしてください! 約束したように、CAShapeLayerプロパティの1つをアニメーション化します。この場合、それはstrokeEndプロパティになります。これは、指定した色でアウトラインを色付けする必要があるグラフ上のポイントを担当します。 また、アニメーションを構成します:継続時間、繰り返し回数、およびアニメーションが時間とともにどれだけ速く成長するか(timingFunction)。



 // ,     ,     0  1 ( 1 —   ) CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; drawAnimation.duration = kAnimationDuration; drawAnimation.repeatCount = 1.0; drawAnimation.fromValue = [NSNumber numberWithFloat:0.0f]; drawAnimation.toValue = [NSNumber numberWithFloat:1.0f]; drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; [circle addAnimation:drawAnimation forKey:@"drawOutlineAnimation"];
      
      





画像を公開します



一般に、新しいことは何もありません。再び円を作成し、そのプロパティの1つの変化を経時的にアニメーション化します。 この場合、半径を変更します-円全体が画面上のちょうど点である場合の1ピクセルから、UIViewの側面の長さの半分になります。これは、完全に開いた画像に対応しますバンキングアプリケーション)。 前に行ったこととのもう1つの違いは、CAShapeLayerオブジェクトの輪郭を描くのではなく、イメージのマスクとして使用することです。 したがって、マスクが切り抜く画像の部分のみが表示され、マスクが大きくなると、マスクを通してより多くの画像が表示されます。 フェンスの隙間をのぞき込んでいると想像してください:隙間はマスクであり、隙間が大きいほど、フェンスの後ろで何が起こっているかをより詳細に見ることができます!



 //      CGFloat initialRadius = 1.0f; CGFloat finalRadius = self.imageToReveal.bounds.size.width / 2.0f; // ,     CAShapeLayer *revealShape = [CAShapeLayer layer]; revealShape.bounds = self.imageToReveal.bounds; //  —   ,   revealShape.fillColor = [UIColor blackColor].CGColor; // ,    :    UIBezierPath *startPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(CGRectGetMidX(self.imageToReveal.bounds) - initialRadius, CGRectGetMidY(self.imageToReveal.bounds) - initialRadius, initialRadius * 2, initialRadius * 2) cornerRadius:initialRadius]; UIBezierPath *endPath = [UIBezierPath bezierPathWithRoundedRect:self.imageToReveal.bounds cornerRadius:finalRadius]; revealShape.path = startPath.CGPath; revealShape.position = CGPointMake(CGRectGetMidX(self.imageToReveal.bounds) - initialRadius, CGRectGetMidY(self.imageToReveal.bounds) - initialRadius); //,     ,       ,     self.imageToReveal.layer.mask = revealShape; // .    path —  ,  ,    .          CABasicAnimation *revealAnimationPath = [CABasicAnimation animationWithKeyPath:@"path"]; revealAnimationPath.fromValue = (__bridge id)(startPath.CGPath); revealAnimationPath.toValue = (__bridge id)(endPath.CGPath); revealAnimationPath.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]; revealAnimationPath.duration = kAnimationDuration/2.0f; revealAnimationPath.repeatCount = 1.0f; //       ,             revealAnimationPath.beginTime = CACurrentMediaTime() + kAnimationDuration/2.0f; revealAnimationPath.delegate = self; //     ,  ,   hidden        , ..     dispatch_time_t timeToShow = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kAnimationDuration/2.0f * NSEC_PER_SEC)); dispatch_after(timeToShow, dispatch_get_main_queue(), ^{ self.imageToReveal.hidden = NO; }); revealShape.path = endPath.CGPath; [revealShape addAnimation:revealAnimationPath forKey:@"revealAnimation"];
      
      





なぜアニメーションをCAAnimationGroupに結合しなかったのかという質問に誰かが苦しんでいる場合(結局、多くの設定は似ています)、これはこれらのアニメーションを異なるレイヤーに適用するためです。 私の知る限り、CAAnimationアニメーションは、単一のレイヤーで動作するという条件の下でのみグループ化できます。 さらに、2番目のアニメーションの遅延を設定することはおそらく最もエレガントなソリューションではないことを認めます。したがって、より良いものを提供できる場合は、恥ずかしがらないでください。



最後に、多くの異なるUIViewでマスクを乱用することはお勧めしません。これらのマスクが多数あると、もちろんアプリケーションの速度が大幅に低下するためです。 最も合理的な解決策は、既に円にトリミングされた画像を使用し、アニメーションの最後にマスクを削除することです。



それだけです。次のものが必要です。











それが何か他のものになった場合-申し訳ありませんが、Xcodeプロジェクトダウンロードして、何がうまくいかなかったかを見つけることができます 。 よろしくお願いします!



All Articles