我正在为iOS的开发而苦苦挣扎,希望有人能够为我提供建议。我想实现一堆看起来像一副纸牌的UIView。用户应该能够通过触摸刷出“卡片”。我想到带有pageingEnabled的UIScrollViews给人的印象是卡是分开的。但是,UIScrollView可以从左侧或右侧出现内容的地方水平滑动。我希望卡片组位于屏幕的中间,以便用户可以从卡片组上刷卡,而不会看到卡出现在左侧或右侧。
另外,我还在考虑使用“触摸方法”(touchesBegan,touchesMoved等)来移动视图。它可以工作...但是我担心我不能重现卡的正确机制(摩擦,滑动短时的弹跳效果等等)。

有人可以为我指出适当的技术或为一些教程提供建议吗?我已经做过一些研究,但是找不到任何有帮助的东西。

这是我想要达到的效果的图像。



我想要实现的是从卡组中刷卡。

非常感谢!

最佳答案

我想您想通过滑动将堆栈顶部的卡牌“洗牌”到堆栈底部,以显示堆栈中的第二张卡,如下所示:



动画在现实生活中更加流畅。我必须使用低帧频来使动画GIF变小。

一种方法是创建UIView的子类来管理卡片视图的堆栈。让我们将经理视图称为BoardView。我们将为它提供两种公共方法:一种用于将顶部卡改组到底部,另一个用于将底部卡改组到顶部。

@interface BoardView : UIView

- (IBAction)goToNextCard;
- (IBAction)goToPriorCard;

@end

@implementation BoardView {
    BOOL _isRestacking;
}


我们将使用_isRestacking变量来跟踪棋盘视图是否使一张纸牌向侧面运动。

BoardView将其所有子视图都视为卡片视图。它负责将它们堆叠在一起,并使顶卡居中。在屏幕快照中,您将较低的卡片偏移了一些随机的数量。我们可以通过随机旋转下部卡片使它看起来更性感。此方法将小的随机旋转应用于视图:

- (void)jostleSubview:(UIView *)subview {
    subview.transform = CGAffineTransformMakeRotation((((double)arc4random() / UINT32_MAX) - .5) * .2);
}


我们希望将其应用于添加的每个子视图:

- (void)didAddSubview:(UIView *)subview {
    [self jostleSubview:subview];
}


每当视图的大小更改或给视图赋予新的子视图时,系统都会发送layoutSubviews。我们将利用它的优势,将所有卡堆叠在板视图边界的中间。但是,如果视图当前正在对卡片进行动画处理,则我们不希望进行布局,因为它会杀死动画。

- (void)layoutSubviews {
    if (_isRestacking)
        return;
    CGRect bounds = self.bounds;
    CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
    UIView *topView = self.subviews.lastObject;
    CGFloat offset = 10.0f / self.subviews.count;
    for (UIView *view in self.subviews.reverseObjectEnumerator) {
        view.center = center;
        if (view == topView) {
            // Make the top card be square to the edges of the screen.
            view.transform = CGAffineTransformIdentity;
        }
        center.x -= offset;
        center.y -= offset;
    }
}


现在,我们准备处理改组。要“转到下一张卡片”,我们需要设置动画,将顶部的卡片移到一边,然后将其移到子视图堆叠顺序的底部,然后再将其设置回中间。我们还需要微调所有其他卡的位置,因为它们都移到了堆栈顶部附近。

- (void)goToNextCard {
    if (self.subviews.count < 2)
        return;


首先,我们将顶卡的移动动画化到侧面。为了使其看起来更性感,我们在移动卡片时会旋转卡片。

    UIView *movingView = self.subviews.lastObject;
    [UIView animateWithDuration:1 delay:0 options:UIViewAnimationCurveEaseInOut animations:^{
        _isRestacking = YES;
        CGPoint center = movingView.center;
        center.x = -hypotf(movingView.frame.size.width / 2, movingView.frame.size.height / 2);
        movingView.center = center;
        movingView.transform = CGAffineTransformMakeRotation(-M_PI_4);
    }


在完成模块中,我们将卡片移到堆栈的底部。

    completion:^(BOOL finished) {
        _isRestacking = NO;
        [self sendSubviewToBack:movingView];


并将现在底下的卡移回堆栈,并推移所有其他卡,我们只需调用layoutSubviews。但是我们不应该直接调用layoutSubviews,所以我们使用正确的API:setNeedsLayout,后跟layoutIfNeeded。我们在动画块内调用layoutIfNeeded,以便将牌动画化到新位置。

        [self setNeedsLayout];
        [UIView animateWithDuration:1 delay:0 options:UIViewAnimationCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction animations:^{
            [self jostleSubview:movingView];
            [self layoutIfNeeded];
        } completion:nil];
    }];
}


那是goToNextCard的结尾。我们可以类似地执行goToPriorCard

- (void)goToPriorCard {
    if (self.subviews.count < 2)
        return;
    UIView *movingView = [self.subviews objectAtIndex:0];
    [UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        _isRestacking = YES;
        CGPoint center = movingView.center;
        center.x = -movingView.frame.size.height / 2;
        movingView.center = center;
        movingView.transform = CGAffineTransformMakeRotation(-M_PI_4);
    } completion:^(BOOL finished) {
        _isRestacking = NO;
        UIView *priorTopView = self.subviews.lastObject;
        [self bringSubviewToFront:movingView];
        [self setNeedsLayout];
        [UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveEaseInOut | UIViewAnimationOptionAllowUserInteraction animations:^{
            [self jostleSubview:priorTopView];
            [self layoutIfNeeded];
        } completion:nil];
    }];
}


一旦有了BoardView,您只需要附加向其发送goToNextCard消息的滑动手势识别器,以及向其发送goToPriorCard消息的另一个滑动手势识别器。并且您需要添加一些子视图以充当卡片。

另一个细节:要使卡的边缘在打结时看起来平滑,需要在UIViewEdgeAntialiasing中将YES设置为Info.plist

您可以在这里找到我的测试项目:http://dl.dropbox.com/u/26919672/cardstack.zip

10-07 23:37