Jakub设计的音乐播放器动画效果
Jakub是一名来自Slovakia的出色的设计师并且在Dribbble上传了一些具有创造力的界面设计。其中有一个效果给我留下了较为深刻的印象,并且相信会很容易吸引到用户。
在介绍代码实现过程之前,我们先看一下这个炫酷的动画效果:
那么这里使用什么技术呢?操纵了动画开始执行的时间。将每个元素一个接着另外一个相继展示在屏幕之上并且整个过程花费相同的时间,所以最终呈现出来的就是一个比较整洁统一的波浪效果,就好像后面出现的元素和前一个元素之间存在着某种粘性一样。
首先,我们需要重新切一些元素来保证我们能够整洁顺利的完成这个动画效果。在这里我选择Photoshop.我们这里不会详细介绍具体的制作过程。
下面是我制作的音乐列表图:
![Upload music1.png failed. Please try again.]
如果你仔细观察原始动画,你就会发现分别有8个不同元素进行了动画:
- 返回箭头和“Dance Club”文本
- “Ministry of Fun”文本
- “Add a Song”按钮
- 5行音乐列表
这8个元素通过控制开始动画时间的不同来实现我们最终所看到的较为吸引人的效果。
首先,需要分别将他们添加到界面才能分别对他们执行动画。如果是一个真实的应用界面的话,这个界面将会通过UITableView
或者是UICollectionView
来有条理的展示列表。使用这两种方式需要你调用一些协议
方法来实现界面数据源,比如通过方法来设置每行数据的显示高度以及每行数据。因为我们没有任何数据,而且我们的主要目的是阐述如何实现这种动画效果,所以主要是通过手动将PNG图片添加到屏幕上,首先是顶部的返回箭头和“Dance Club”文本。你可以创建一个**Music Player **项目并实现下列代码:
// Define a variable to get the full width of the window as we’ll
// be using this value a lot.
CGFloat windowWidth = self.window.bounds.size.width;
// Add the background to the window
UIImageView *backgroundView = [[UIImageView alloc] initWithFrame:self.window.bounds];
backgroundView.image = [UIImage imageNamed:@"background"];
[self.window addSubview:backgroundView];
// Add the arrow and top label
UIImageView *arrowView =
[[UIImageView alloc] initWithFrame:CGRectMake(0, 0, windowWidth, 45)];
arrowView.image = [UIImage imageNamed:@"arrow"];
[self.window addSubview:arrowView];
这里只是简单的将这些View添加到屏幕上,并没有产生一些绚丽的动画效果。背景图片命名为@"background"
,而@"arrow"
图片则是包括了顶部的返回按钮以及“Dance Club”文本,因为他们需要一起执行动画,将他们做在一个图片中应该会更简单。
现在界面效果如下图所示:
让我们来继续添加更多内容吧!
// Ministry of Fun image
UIImageView *ministryView =
[[UIImageView alloc] initWithFrame:CGRectMake(0, 57, windowWidth, 28)];
ministryView.image = [UIImage imageNamed:@"ministry"];
[self.window addSubview:ministryView];
// Add a Song button
UIButton *addButton = [UIButton buttonWithType:UIButtonTypeCustom];
[addButton setImage:[UIImage imageNamed:@"add-button"] forState:UIControlStateNormal];
[addButton setImage:[UIImage imageNamed:@"add-button-pressed"]
forState:UIControlStateHighlighted];
[addButton setFrame:CGRectMake(0, 102, windowWidth, 45)];
[self.window addSubview:addButton];
添加了一个展示“Ministry of Fun”的imageView,并且创建了一个“Add a Song”按钮。在这里偷了个懒,只是设置了Button
的UIImageView
属性,因只是需要设置按钮的正常和电机两种状态,所以通过调用-setImage:forState:
来分别设置两种状态下的不同图片。
现在的界面效果以及按钮的点击效果如下图所示:
高亮状态下的图片背景色是白色而不是white stroke
.
现在需要往屏幕上添加音乐列表了,他们也是UIImageView
.
// Katy Perry row
UIImageView *firstRow =
[[UIImageView alloc] initWithFrame:CGRectMake(0, 170, windowWidth, 80)];
firstRow.image = [UIImage imageNamed:@"1st-row"];
[self.window addSubview:firstRow];
// Shakira row
UIImageView *secondRow =
[[UIImageView alloc] initWithFrame:CGRectMake(0, 170+80, windowWidth, 80)];
secondRow.image = [UIImage imageNamed:@"2nd-row"];
[self.window addSubview:secondRow];
// Pitbull row
UIImageView *thirdRow =
[[UIImageView alloc] initWithFrame:CGRectMake(0, 170+160, windowWidth, 80)];
thirdRow.image = [UIImage imageNamed:@"3rd-row"];
[self.window addSubview:thirdRow];
// Lana del Rey row
UIImageView *fourthRow =
[[UIImageView alloc] initWithFrame:CGRectMake(0, 170+240, windowWidth, 80)];
fourthRow.image = [UIImage imageNamed:@"4th-row"];
[self.window addSubview:fourthRow];
// HEX row
UIImageView *fifthRow =
[[UIImageView alloc] initWithFrame:CGRectMake(0, 170+320, windowWidth, 80)];
fifthRow.image = [UIImage imageNamed:@"5th-row"];
[self.window addSubview:fifthRow];
你应该已经发现了,列表每行的frame
的Y坐标之间之间都有一个简单的数学等式。每行都是80px高,所以下一个的Y坐标都在前一个的Y坐标的基础上加上了80。同样也可以通过Auto Layout
来添加列表,但是对于一个例子没有必要非要这么做。
下图是设置动画效果之前的截图效果:
但是,我们并不是想要整个界面展示这种效果,整个练习的重点是图片从屏幕之外并且从右边开始,然后移动向屏幕左边,并最终展示在终点位置,而不是一开始就出现在最终位置。
让我们重新审视并且修改我们的代码,使得元素的frame
属性的X坐标值不再是0,而是windowWidth
。这会使得每个元素的左侧对齐屏幕的右边缘。
// Add the arrow and top label
UIImageView *arrowView =
[[UIImageView alloc] initWithFrame:CGRectMake(windowWidth, 0, windowWidth, 45)];
arrowView.image = [UIImage imageNamed:@"arrow"];
[self.window addSubview:arrowView];
// Ministry of Fun label
UIImageView *ministryView =
[[UIImageView alloc] initWithFrame:CGRectMake(windowWidth, 57, windowWidth, 56/2)];
ministryView.image = [UIImage imageNamed:@"ministry"];
[self.window addSubview:ministryView];
// Add a Song button
UIButton *addButton = [UIButton buttonWithType:UIButtonTypeCustom];
[addButton setImage:[UIImage imageNamed:@"add-button"]
forState:UIControlStateNormal];
[addButton setImage:[UIImage imageNamed:@"add-button-pressed"]
forState:UIControlStateHighlighted];
[addButton setFrame:CGRectMake(windowWidth, 102, windowWidth, 45)];
[self.window addSubview:addButton];
// Katy Perry row
UIImageView *firstRow =
[[UIImageView alloc] initWithFrame:CGRectMake(windowWidth, 170, windowWidth, 80)];
firstRow.image = [UIImage imageNamed:@"1st-row"];
[self.window addSubview:firstRow];
// Shakira row
UIImageView *secondRow =
[[UIImageView alloc] initWithFrame:CGRectMake(windowWidth, 170+80, windowWidth, 80)];
secondRow.image = [UIImage imageNamed:@"2nd-row"];
[self.window addSubview:secondRow];
// Pitbull row
UIImageView *thirdRow =
[[UIImageView alloc] initWithFrame:CGRectMake(windowWidth, 170+160, windowWidth, 80)];
thirdRow.image = [UIImage imageNamed:@"3rd-row"];
[self.window addSubview:thirdRow];
// Lana del Rey row
UIImageView *fourthRow =
[[UIImageView alloc] initWithFrame:CGRectMake(windowWidth, 170+240, windowWidth, 80)];
fourthRow.image = [UIImage imageNamed:@"4th-row"];
[self.window addSubview:fourthRow];
// HEX row
UIImageView *fifthRow =
[[UIImageView alloc] initWithFrame:CGRectMake(windowWidth, 170+320, windowWidth, 80)];
fifthRow.image = [UIImage imageNamed:@"5th-row"];
[self.window addSubview:fifthRow];
现在我们开始通过block
方式将返回按钮和“Dance Club”文本移动到屏幕左侧。
[UIView animateWithDuration:1.1 delay:0 usingSpringWithDamping:0.3
initialSpringVelocity:0 options:0 animations:^{
[arrowView setFrame:CGRectMake(0, 0, windowWidth, 45)];
} completion:NULL];
这段代码表示,这个弹簧效果动画会持续1.1秒,并且阻尼(damping)为0.3.效果如下图所示:
但是上面的弹簧动画效果来的太快和生硬,这样会给用户一种紧张的感觉,这样的动画效果并不会提升用户体验。每一种动画效果都会影响用户心情,而这也包括一些不好的情绪。
将动画持续时间改为2.1秒后,我们再来感受下现在的效果:
![Uploading musicbutton3_707114.gif . . .]
这种效果与Jakub的最初的设计效果相比,弹性又太大了,所以我们仍然需要调整这种弹簧效果的阻尼(damping)值。将该值从0.3提高到0.6(这个值越接近1弹性效果就会越来越不明显)。因为我们仍然需要那么一点点的弹性效果,接下来看下现在的效果吧:
那,现在的效果已经也还可以了,通过使用iOS7的弹簧动画效果,可以修改某些参数来达到你想要实现的一些弹簧效果。但是,就我个人而言,我仍然觉得JNWSpringAnimation提供的一些方法更加方便,而且更能实现相对真实的物理弹簧运动效果。
现在我们重新对其它元素执行动画。每个元素都会比前一个元素稍微晚点执行动画。而且,我需要控制在应用出现后多久执行所有动画效果。
CGFloat initialDelay = 1.0f;
CGFloat stutter = 0.3f;
// Animate the top arrow image
[UIView animateWithDuration:2.1 delay:initialDelay
usingSpringWithDamping:0.6 initialSpringVelocity:0 options:0 animations:^{
[arrowView setFrame:CGRectMake(0, 0, windowWidth, 45)];
} completion:NULL];
// Animate the image label Ministry of Fun
[UIView animateWithDuration:2.1 delay:initialDelay + (1 * stutter)
usingSpringWithDamping:0.6 initialSpringVelocity:0 options:0 animations:^{
[ministryView setFrame:CGRectMake(0, 57, windowWidth, 28)];
} completion:NULL];
通过定义一个CGFloat
类型的变量stutter
来存储一个固定动画执行间隔。这个数值能够决定整个动画效果给用户所带来的感觉。各个元素动画间隔时间过长则会给用户一种动画执行不太连贯的效果,太短又会使得最终效果不像我们最初想要做的波浪效果。
第一个动画中的delay
属性是设置的initialDelay
属性值,因为这个是第一个执行动画的元素,而第二个执行动画的元素的delay
属性值为initialDelay + (1 * stutter)
,并以此类推,效果如下所示:
到这里为止,动画已经差不多了,但是很难凭现在的两个元素的动画效果来断定动画效果的好坏,现在需要接着完成接下来的所有元素的动画效果。
[UIView animateWithDuration:2.1 delay:initialDelay + (2 * stutter)
usingSpringWithDamping:0.6 initialSpringVelocity:0 options:0 animations:^{
[addButton setFrame:CGRectMake(0, 102, windowWidth, 45)];
} completion:NULL];
[UIView animateWithDuration:2.1 delay:initialDelay + (3 * stutter)
usingSpringWithDamping:0.6 initialSpringVelocity:0 options:0 animations:^{
[firstRow setFrame:CGRectMake(0, 170, windowWidth, 80)];
} completion:NULL];
[UIView animateWithDuration:2.1 delay:initialDelay + (4 * stutter)
usingSpringWithDamping:0.6 initialSpringVelocity:0 options:0 animations:^{
[secondRow setFrame:CGRectMake(0, 170+80, windowWidth, 80)];
} completion:NULL];
[UIView animateWithDuration:2.1 delay:initialDelay + (5 * stutter)
usingSpringWithDamping:0.6 initialSpringVelocity:0 options:0 animations:^{
[thirdRow setFrame:CGRectMake(0, 170+160, windowWidth, 80)];
} completion:NULL];
[UIView animateWithDuration:2.1 delay:initialDelay + (6 * stutter)
usingSpringWithDamping:0.6 initialSpringVelocity:0 options:0 animations:^{
[fourthRow setFrame:CGRectMake(0, 170+240, windowWidth, 80)];
} completion:NULL];
[UIView animateWithDuration:2.1 delay:initialDelay + (7 * stutter)
usingSpringWithDamping:0.6 initialSpringVelocity:0 options:0 animations:^{
[fifthRow setFrame:CGRectMake(0, 170+320, windowWidth, 80)];
} completion:NULL];
现在整体的动画效果如下图所示:
但是这个效果仍然不是最好的,因为元素动画之间的间隔时间好像太长了,导致整体的动画效果不是特别流畅。我们将元素执行动画的时间间隔stutter
从0.3降到0.15来看看具体的展示效果:
![Upload musicbutton8.gif failed. Please try again.]
这已经与我们的设想很接近了,我们仍然可以继续调整使得最终效果更加流畅统一,就好像元素微微的拉着下一个元素一样,将stutter
继续降低到0.06:
现在我们可以看到,最终呈现的效果已经与最初的设计效果很接近了,而且并没有花费太长时间来实现。
有趣的position动画效果
当我的新闻应用启动的时候,首先通过发送网络请求来下拉刷新最新的文章。我们需要利用返回数据来构建UITableView
数据,每一行表示一条文章数据。有的应用在返回数据之前隐藏整个列表,有的则会将cell一个接一个的展示在合适的位置,而其他的则是不运用任何动画效果。我选择刚才我们创建的那种动画效果来实现,但不是水平方向的动画,而是从底部垂直方向的波浪效果。
要完成这个动画效果,我们需要一步步的分解整个动画效果:
- 当请求返回数据的时候调用
[self.tableView reloadData]
方法,会立即展示出列表。所以我们需要将列表的opacity
属性设置为0,这样我就可以控制用户在什么时候看到列表。 - 然后调用
[self.tableView reloadData]
会利用返回数据刷新列表顶部数据,在这个时候所有的数据都在他们应该处在的位置,但是这个时候opaticy
属性值仍然为0,所以这个时候屏幕上看不到任何元素。 - 遍历所有的
UITableViewCell
,通过改变他们的positon
属性值来将他们移动到屏幕的最底部,直到所有的行都不在屏幕上为止。 - 因为所有行都在屏幕底部而不显示在屏幕上,通过将
alpha
属性设置为1,将列表显示,但是这个时候仍然看不到,因为他们的position
全都在屏幕之外。 - 然后通过改变每行的
position
来将每行数据展示在适当的位置。
这就是实现这种效果所需的所有步骤:
// Make the table invisible, then reload its data
self.tableView.alpha = 0.0f;
[self.tableView reloadData];
// Store a delta timing variable so I can tweak the timing delay
// between each row’s animation and some additional
CGFloat diff = .05;
CGFloat tableHeight = self.tableView.bounds.size.height;
NSArray *cells = [self.tableView visibleCells];
// Iterate across the rows and translate them down off the screen
for (NSUInteger a = 0; a < [cells count]; a++) {
UITableViewCell *cell = [cells objectAtIndex:a];
if ([cell isKindOfClass:[UITableViewCell class]]) {
// Move each cell off the bottom of the screen by translating its Y position
cell.transform = CGAffineTransformMakeTranslation(0, tableHeight);
}
}
// Now that all rows are off the screen, make the tableview opaque again
self.tableView.alpha = 1.0f;
// Animate each row back into place
for (NSUInteger b = 0; b < [cells count]; b++) {
UITableViewCell *cell = [cells objectAtIndex:b];
[UIView animateWithDuration:1.6 delay:diff*b usingSpringWithDamping:0.77
initialSpringVelocity:0 options:0 animations:^{
cell.transform = CGAffineTransformMakeTranslation(0, 0);
} completion:NULL];
}