如上配图,最近有项目需求,使用原生代码实现类似动画效果。还算是遇到的比较好玩的一个小效果。前后用了个把小时实现了这个效果。下面把大致实现思路和方式总结一下。
看到这张UI效果图,你想怎么画它。容你思考1分钟,嘀嗒...嘀嗒...
emmm 想到了吧~ 我这里简述两种方式:
1. 创建9个控件,计算位置排布。
2. UICollectionView
本文中的实现,使用的是UICollectionView。至于第一种方式,预计要写的代码不够简洁,这里就不展示了,因为我没有写具体的代码。
开始~
既然使用UICollectionView自定义布局,创建9个单元格即可。简单~
重点:
继承UICollectionViewFlowLayout创建类,书写自定义布局代码。
- (void)prepareLayout {
[super prepareLayout];
self.attributrAry = [NSMutableArray array];
for(int i = 0; I < self.itemCount; i ++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:0];
UICollectionViewLayoutAttributes *attrs = [self layoutAttributesForItemAtIndexPath:indexPath];
[self.attributrAry addObject:attrs];
}
}
- (CGSize)collectionViewContentSize {
return self.collectionView.frame.size;
}
- (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
return [self.attributrAry copy];
}
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
self.itemCount = (NSInteger)[self.collectionView numberOfItemsInSection:0];
CGFloat radius = MIN(self.collectionView.frame.size.width, self.collectionView.frame.size.height) / 2;
CGFloat center_x = self.collectionView.frame.size.width * 0.5;
CGFloat center_y = self.collectionView.frame.size.height * 0.5;
UICollectionViewLayoutAttributes *attris = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath];
attris.size = CGSizeMake(85, 85);
CGFloat angle = (2 * M_PI / self.itemCount) * indexPath.item - M_PI_2;
CGFloat xx = center_x + cosf(angle) * (radius - 85 / 2);
CGFloat yy = center_y + sinf(angle) * (radius - 85 / 2);
attris.center = CGPointMake(xx, yy);
return attris;
}
每个单元格位置计算核心代码:
CGFloat angle = (2 * M_PI / self.itemCount) * indexPath.item - M_PI_2;
CGFloat xx = center_x + cosf(angle) * (radius - 85 / 2);
CGFloat yy = center_y + sinf(angle) * (radius - 85 / 2);
attris.center = CGPointMake(xx, yy);
观看上面第二张插图,我们需要把第一个单元格放在正上方。(2 * M_PI / self.itemCount) * indexPath.item;这句代码书写后,你会看到第一个单元格在最右边,所以我这里在后面减了一个M_PI_2,用来调整位置。
以上即是实现布局的代码,拿来创建对应的UICollectionView。
- (UICollectionView *)turnDataColView {
if (!_turnDataColView){
HXTurnCircleLayout *layout = [[HXTurnCircleLayout alloc] init];
_turnDataColView = [[UICollectionView alloc] initWithFrame:CGRectMake((SCREEN_WIDTH - 334 - 30) / 2, 95 - 20, 334 + 30, 334 + 30) collectionViewLayout:layout];
_turnDataColView.delegate = self;
_turnDataColView.dataSource = self;
[_turnDataColView registerClass:[HXTurnDataCell class] forCellWithReuseIdentifier:@"HXTurnDataCell"];
_turnDataColView.showsHorizontalScrollIndicator = NO;
_turnDataColView.showsVerticalScrollIndicator = NO;
_turnDataColView.backgroundColor = [UIColor clearColor];
_turnDataColView.userInteractionEnabled = NO;
}
return _turnDataColView;
}
把UICollectionView的代理方法添加好后,运行吧~
是不是发现,效果已经实现了。那么开始第二步,摩天轮的效果动画了。
这次思考3分钟吧。顺便再回头观察一下第一张配图,仔细看,有发现什么细节吗?
......
对的,底部在转动,上层的控件也在动,但上层的方向始终是垂直向上的。就像摩天轮一下,人坐在里面不能头朝下的,对吧~
到这里,好像觉得创建9个小控件,然后让这些小控件在一个圆周上做移动会比较好一些,改变每一个小控件的center。
emmm 这是一种实现方式,但是我数学不好,没有去这么弄。
话说回来,使用的是UICollectionView,底层控件的转动效果让UICollectionView绕Z轴转动就可以了。所以给UICollectionView添加了一个动画,0.1秒转动1度吧。
[UIView animateWithDuration:0.1 delay:0.0f options:options animations:^{
self.turnDataColView.transform = CGAffineTransformRotate(self.turnDataColView.transform, M_PI / 180);
}completion:^(BOOL finished) {
}];
效果不错,开始转动了。
为了让它保持持续转动,我们添加以下代码。(isStart用来标记开始/结束)
- (void)turnWithOptions:(UIViewAnimationOptions)options {
[UIView animateWithDuration:0.1 delay:0.0f options:options animations:^{
self.turnDataColView.transform = CGAffineTransformRotate(self.turnDataColView.transform, M_PI / 180);
}completion:^(BOOL finished) {
if (finished) {
if (self.isStart) {
[self turnWithOptions:UIViewAnimationOptionCurveLinear];
} else if (options != UIViewAnimationOptionCurveEaseOut) {
[self turnWithOptions:UIViewAnimationOptionCurveEaseOut];
}
}
}];
}
那单元格怎么弄?先想一想,别着急看下面一段文字。
OK~
请在脑海里模拟下面一段文字的内容,当然你也可以拿笔简单在纸上画一下。
以一个单元格为准,我们使用最顶部的那个。假如父控件是顺时针转动(上面代码的效果)。单元格随着父控件转动。注意我们的目的,是让单元格保持垂直。那么,我们选过程中一个比较特殊的位置,即父控件转动了45度的时候,这个单元格的倾斜也是45度,此时我们把单元格逆时针转动45度,它刚好垂直。再分别选着几个位置,90度,180度...,可得到相同的效果。
明白了吗?
结论:我们的父控件每转动1度,我们把子控件向反方向转动1度,就是我们要实现的效果。
那就相当简单了,相信我不把代码贴出来,你们也知道代码怎么写了。
到此,我们效果已经实现了。感谢猿同胞们在百忙中浏览到这篇文章。
感恩常在~
...
...
...
结论的实现代码,在父控件的转动效果中添加一句代码(这里不是为了凑字数 /斜眼笑):
- (void)turnWithOptions:(UIViewAnimationOptions)options {
[UIView animateWithDuration:0.1 delay:0.0f options:options animations:^{
self.turnDataColView.transform = CGAffineTransformRotate(self.turnDataColView.transform, M_PI / 180);
self.cell0.transform = CGAffineTransformRotate(self.cell0.transform, -M_PI / 180);
}completion:^(BOOL finished) {
if (finished) {
if (self.isStart) {
[self turnWithOptions:UIViewAnimationOptionCurveLinear];
} else if (options != UIViewAnimationOptionCurveEaseOut) {
[self turnWithOptions:UIViewAnimationOptionCurveEaseOut];
}
}
}];
}
开始转动动画:
- (void)startTurnAni {
if (!self.isStart) {
self.isStart = YES;
NSIndexPath *idxP0 = [NSIndexPath indexPathForItem:0 inSection:0];
HXTurnDataCell *cell0 = (HXTurnDataCell *)[self.turnDataColView cellForItemAtIndexPath:idxP0];
self.cell0 = cell0;
[self turnWithOptions:UIViewAnimationOptionCurveEaseIn];
}
}
结束转动动画:
- (void)stopTurnAni {
self.isStart = NO;
self.turnDataColView.transform = CGAffineTransformIdentity;
self.cell0.transform = CGAffineTransformIdentity;
}
这次真的结尾啦~
感谢~