我正在为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