5

とを使用してCALayer、棒グラフを所定の位置にアニメーション化します。棒グラフは「セグメント」で構成されており、. 次に、それらをサブレイヤーとしてループに追加します。最後に、小さな四角形ごとにアニメーションを追加して、四角形をチャート内の所定の位置にドロップします。CAReplicatorLayerCABasicAnimationCALayerCAReplicatorLayer

アニメーションの最終状態は次のとおりです。

アニメーションの正しい最終状態

そして、アニメーションの途中で次のようになります。

アニメーション状態

これを実現するコードは次のとおりです。

#define BLOCK_HEIGHT 6.0f
#define BLOCK_WIDTH 8.0f

@interface TCViewController ()

@property (nonatomic, strong) NSMutableArray * blockLayers;         // blocks that fall from the sky
@property (nonatomic, strong) NSMutableArray * replicatorLayers;    // replicator layers that will replicate the blocks into position

@end

@implementation TCViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor lightGrayColor];

    self.blockLayers = [NSMutableArray array];
    self.replicatorLayers = [NSMutableArray array];

    NSArray * dataBlocksData = @[@1,@2,@3,@1,@5,@2,@11,@14,@5,@12,@10,@3,@7,@6,@3,@4,@1];

    __block CGFloat currentXPosition = 0.0f;

    [dataBlocksData enumerateObjectsUsingBlock:^(NSNumber * obj, NSUInteger idx, BOOL *stop) {

        currentXPosition += BLOCK_WIDTH + 2.0f;

        if (obj.integerValue == 0) return;

        // replicator layer for data blocks in a column
        CAReplicatorLayer * replicatorLayer = [[CAReplicatorLayer alloc] init];
        replicatorLayer.frame = CGRectMake(currentXPosition, 0.0f, BLOCK_WIDTH, 200.0f);

        // single data block layer (must be new for each replicator layer. can't reuse existing layer)
        CALayer * blockLayer = [[CALayer alloc] init];
        blockLayer.frame = CGRectMake(0, 200-BLOCK_HEIGHT, BLOCK_WIDTH, BLOCK_HEIGHT);
        blockLayer.backgroundColor = [UIColor whiteColor].CGColor;
        [replicatorLayer addSublayer:blockLayer];

        replicatorLayer.instanceCount = obj.integerValue;
        replicatorLayer.instanceDelay = 0.1f;
        replicatorLayer.instanceTransform = CATransform3DMakeTranslation(0, -BLOCK_HEIGHT, 0);    // negative height moves back up the column

        [self.blockLayers addObject:blockLayer];                
        [self.replicatorLayers addObject:replicatorLayer];      
    }];
}

- (void)viewDidLayoutSubviews {
    // add replicator layers as sublayers
    [self.replicatorLayers enumerateObjectsUsingBlock:^(CAReplicatorLayer * obj, NSUInteger idx, BOOL *stop) {
        [self.view.layer addSublayer:obj];
    }];
}

- (void)viewDidAppear:(BOOL)animated {

    // add animations to each block layer
    [self.blockLayers enumerateObjectsUsingBlock:^(CALayer * obj, NSUInteger idx, BOOL *stop) {
        // position animation
        CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
        animation.fromValue = @(-300);
        animation.toValue = @(0);
        animation.duration = 1.0f;
        animation.fillMode = kCAFillModeBoth;
        animation.removedOnCompletion = NO;
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
        [obj addAnimation:animation forKey:@"falling-block-animation"];
    }];
}

最後に、これがキャッチです。no timingFunctionLinear関数または関数を指定するとEaseIn、アニメーションは完全には完了しません。ブロックが完全にはまらないように。完成に近づいているように見えますが、完全ではありません。ちょうど 1 つまたは 2 つのフレームをオフにします。タイミング関数の割り当てを交換する-viewDidAppear:と、次のような奇妙な結果が得られます。

ここに画像の説明を入力

さあ、試してみてください。関数とまったく同じコントロールポイント(linkEaseIn )で関数を指定してみました:

// use control points for an EaseIn timing:
animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.42 :0.00 :1.0 :1.0];

// or simply specify the ease in timing function:
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];

何らかの理由EaseOutで、Defaultタイミング関数は正常に機能しますが、補間の曲線の長さのために、スタイル関数の使いやすさが視覚的に完了するのに時間がかかるように見えますか?

問題を絞り込むために多くのことを試しました:

  1. これらの構造をループせず、同じアニメーションで 1 つのレイヤーと 1 つのレプリケーター レイヤーのみを使用します。つまり、1 つの列だけをアニメーション化します。運がない。ループは問題ではありません。
  2. 作成、サブレイヤーの追加、アニメーションの追加のコードをすべて に入れ-viewDidAppearます。アニメーションがビューのライフサイクルの早い段階で追加された場合でも後で追加された場合でも、違いはないようです。
  3. 構造にプロパティを使用する。変わりはない。
  4. 私の構造にプロパティを使用していません。繰り返しますが、違いはありません。
  5. 列を正しい「最終」状態のままにするために、アニメーションを削除するアニメーション完了のコールバックを追加しました。これは良くありませんでしたが、試してみる価値があります。
  6. この問題は、iOS 6 と 7 の両方に存在します。

タイミング関数を使用したいEaseInのは、何かが所定の位置に収まるのに最も見栄えの良いアニメーションだからです。CAReplicatorLayerまた、私がやりたいことを正確に実行するので、 も使用したいと思います。

EaseInでは、私が使用しているタイミング関数の何が問題なのですか? これはバグですか?

4

1 に答える 1

1

これは CAReplicatorLayer と kCAMediaTimingFunctionEaseIn タイミング関数の組み合わせのバグのように見えることに同意します。

回避策として、3 つのキーフレームを持つキーフレーム アニメーションを使用することをお勧めします。最初の 2 つのキーフレームは、イーズインのタイミングで目的のアニメーションを定義します。最後のキーフレームは 2 番目のキーフレームと同じになるように定義されていますが、アニメーションが最後のキーフレームにとどまる時間が追加されます。これにより、レプリケーター レイヤーが最後に複製されたレイヤーを最終状態にアニメートできるようになります。

したがって、私の提案は次のとおりです。

CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.y"];

//  extra keyframe duplicating final state
animation.values = @[@-300.0f, @0.0f, @0.0f];

//  double length of original animation, with last half containing no actual animation
animation.keyTimes = @[@0.0, @0.5, @1.0];
animation.duration = 2.0f;

animation.fillMode = kCAFillModeBoth;
animation.removedOnCompletion = NO;

//  ease-in for first half of animation; default after
animation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
[obj addAnimation:animation forKey:@"falling-block-animation"];
于 2014-06-25T18:58:34.140 に答える