最近网易新闻客户端更新之后个人感觉不如从前,以前点击左上角的汉堡包菜单按钮的时候,整个屏幕会右移然后出现列表菜单,还有列表的动画效果(外围的圆圈会先放大然后缩小,选中的栏目图标有特殊的动画)。在网易新闻放弃这些动画效果之后,让我想要自己制作这么一种动画效果(使用的是CABaseAnimation类制作的动画,所以想要学习根据图像路径进行绘制的童鞋对不住啦)。下面是部分动画效果:
首先是第一行的新闻栏目默认选中状态下点击菜单按钮时,图标会从小到大放大
选中第二行的阅读界面,然后重新点开按钮时,阅读的图标会有淡入效果
翻滚吧!第三行的视频图标则是沿x轴进行360°的旋转
第四行的查找沿y轴旋转一周
最后一行的设置是旋转一圈
界面的设计思想是在appDelegate的window中加上一层tableview,用于显示界面右移之后的列表,并用sendSubviewToBack:方法保证tableview在最底层。
上面图标的类命名为MenuList,下面放上代码:
LXDMenuList.h
<span style="font-size:14px;">#import <UIKit/UIKit.h>
extern NSString * const LXDResetNewsType;
typedef void(^LXDClickMenuHandler)(Class typeControllerClass);
@interface LXDMenuList : UIView
- (void)startFlash;
@end</span>
<span style="font-size:14px;">#import "LXDMenuList.h"
#import "LXDAnimation.h"
#define kAngleToRadius(angle) ((angle) * (3.14159) / (180.0))
static NSString * const kFlashAnimationGroup = @"kFlashAnimationGroup";
static NSString * const kAnimation = @"kAnimation";
static inline UIColor * kRGB(float red, float green, float blue, float alpha)
{
return [UIColor colorWithRed: red/255.0 green: green/255.0 blue: blue/255.0 alpha: alpha];
}
@interface LXDMenuList ()<UITableViewDataSource, UITableViewDelegate>
@end
@implementation LXDMenuList
{
NSArray * imageList_;
NSArray * typeList_;
NSArray * colorArr_;
NSMutableArray * circleArr_;
UITableView * tableview_;
UITableViewCell * choosCell_;
NSArray * animationArr_;
}
- (instancetype)initWithFrame: (CGRect)frame
{
if (self = [super initWithFrame: frame]) {
[self initDataSourceArr];
[self createTableViewWithSize: frame.size];
}
return self;
}
/**
* 初始化数据源数组
*/
- (void)initDataSourceArr
{
circleArr_ = [NSMutableArray new];
colorArr_ = @[
kRGB(248, 175, 255, 0.8),
kRGB(250, 255, 196, 0.8),
kRGB(120, 255, 219, 0.8),
kRGB(160, 245, 255, 0.8),
kRGB(255, 209, 143, 0.8)
];
imageList_ = @[@"tableview_news", @"tableview_read", @"tableview_video", @"tableview_find", @"tableview_setting"];
typeList_ = @[@"新闻", @"阅读", @"视频", @"查找", @"设置"];
animationArr_ = @[
[LXDAnimation scaleAnimation],
[LXDAnimation opacityAnimation],
[LXDAnimation flipAtYAnimation],
[LXDAnimation flipAtXAnimation],
[LXDAnimation rotateAnimation]
];
}
/**
* 创建tableview
*/
- (void)createTableViewWithSize: (CGSize)size
{
tableview_ = [[UITableView alloc] initWithFrame: CGRectMake(0, 50, size.width, size.height - 50)];
tableview_.backgroundColor = [UIColor clearColor];
tableview_.rowHeight = 80.0f;
tableview_.delegate = self;
tableview_.dataSource = self;
tableview_.tableFooterView = [[UIView alloc] initWithFrame: CGRectMake(0, 0, tableview_.frame.size.width, 10)];
tableview_.tableFooterView.backgroundColor = [UIColor clearColor];
tableview_.separatorColor = [UIColor colorWithRed: 0.93 green: 0.93 blue: 0.93 alpha: 0.1];
choosCell_ = [tableview_ cellForRowAtIndexPath: [NSIndexPath indexPathForRow: 0 inSection: 0]];
choosCell_.tag = 0;
[self addSubview: tableview_];
}
/**
* 重新设置cell上面的控件布局
*/
- (void)createSubViewLocationOnCell: (UITableViewCell *)cell rowHeight: (CGFloat)rowHeight indexPath: (NSIndexPath *)indexPath
{
CGRect imageRect = CGRectMake(self.frame.size.width * 0.75 - 120, (rowHeight - 32) / 2, 32, 32);
UIImageView * imageView = [[UIImageView alloc] initWithFrame: imageRect];
imageView.tag = 100;
[cell.contentView addSubview: imageView];
UILabel * textLabel = [[UILabel alloc] initWithFrame: CGRectMake(imageRect.origin.x + 60, 0, 200, 30)];
textLabel.center = CGPointMake(textLabel.center.x, rowHeight / 2);
textLabel.tag = 101;
textLabel.textColor = colorArr_[indexPath.row];
[cell.contentView addSubview: textLabel];
tableview_.separatorInset = UIEdgeInsetsMake(0, imageRect.origin.x, 0, 0);
UIView * circleView = [self createCircleViewOnCenter: imageView.center circleRadius:(rowHeight - 30) / 2 index: indexPath.row];
[circleArr_ addObject: circleView];
[cell.contentView addSubview: circleView];
}
/**
* 创建圆圈,在菜单栏显示时触发动画
*
* @param
center 圆圈的中心点
circleRadius 圆圈半径
*
* @return 圆圈视图
*/
- (UIView *)createCircleViewOnCenter: (CGPoint)center circleRadius: (CGFloat)radius index: (NSInteger)index
{
UIView * circleView = [[UIView alloc] initWithFrame: CGRectMake(center.x - radius, center.y - radius, radius * 2, radius * 2)];
UIColor * circleColor = colorArr_[index];
CAShapeLayer * circleLayer = [CAShapeLayer layer];
circleLayer.path = [UIBezierPath bezierPathWithArcCenter: CGPointMake(radius, radius) radius: radius startAngle: kAngleToRadius(0) endAngle: kAngleToRadius(360) clockwise: YES].CGPath;
circleLayer.fillColor = [UIColor clearColor].CGColor;
circleLayer.strokeColor = circleColor.CGColor;
[circleView.layer addSublayer: circleLayer];
return circleView;
}
/**
* 点击菜单栏后显示动画
*/
- (void)startFlash
{
for (UIView * circle in circleArr_) {
[circle.layer removeAnimationForKey: kFlashAnimationGroup];
[circle.layer addAnimation: [LXDAnimation scaleAnimationGroup] forKey: kFlashAnimationGroup];
}
[[choosCell_.contentView viewWithTag: 100].layer removeAnimationForKey: kAnimation];
[[choosCell_.contentView viewWithTag: 100].layer addAnimation: animationArr_[choosCell_.tag] forKey: kAnimation];
}
#pragma mark -- UITableViewDataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return typeList_.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: @"Cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault reuseIdentifier: @"Cell"];
[self createSubViewLocationOnCell: cell rowHeight: tableview_.rowHeight indexPath: indexPath];
cell.backgroundColor = [UIColor clearColor];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
[[cell.contentView viewWithTag: 100] setValue: [UIImage imageNamed: imageList_[indexPath.row]] forKey: @"image"];
[[cell.contentView viewWithTag: 101] setValue: typeList_[indexPath.row] forKey: @"text"];
return cell;
}
#pragma mark -- UITableViewDelegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
choosCell_ = [tableview_ cellForRowAtIndexPath: indexPath];
choosCell_.tag = indexPath.row;
[[NSNotificationCenter defaultCenter] postNotificationName: LXDResetNewsType object: typeList_[indexPath.row]];
}
@end</span>
通过通知中心在点击菜单栏按钮的时候发送通知开始列表内容的动画,所有的动画独立的封装在了另一个动画类中,使用类方法获取,然后加到layer上面。为了防止多次快速的点击导致app的崩溃,在动画开始前移除layer上面所有的动画
LXDAnimation.h
<span style="font-size:14px;">#import <Foundation/Foundation.h>
@class CABasicAnimation;
@class CAAnimationGroup;
@interface LXDAnimation : NSObject
+ (CAAnimationGroup *)scaleAnimationGroup;
+ (CABasicAnimation *)opacityAnimation;
+ (CABasicAnimation *)scaleAnimation;
+ (CABasicAnimation *)rotateAnimation;
+ (CABasicAnimation *)flipAtXAnimation;
+ (CABasicAnimation *)flipAtYAnimation;
@end</span>
LXDAnimation.m
<span style="font-size:14px;">#import "LXDAnimation.h"
#import <UIKit/UIKit.h>
@implementation LXDAnimation
+ (CAAnimationGroup *)scaleAnimationGroup
{
CABasicAnimation * extendAnimation = [CABasicAnimation animationWithKeyPath: @"transform.scale"];
extendAnimation.toValue = [NSValue valueWithCATransform3D: CATransform3DMakeScale(5.0, 5.0, 1.0)];
CABasicAnimation * reduceAnimation = [CABasicAnimation animationWithKeyPath: @"transform.scale"];
reduceAnimation.toValue = [NSValue valueWithCATransform3D: CATransform3DMakeScale(0.01, 0.01, 1.0)];
CABasicAnimation * revertAnimation = [CABasicAnimation animationWithKeyPath: @"transform.scale"];
revertAnimation.toValue = [NSValue valueWithCATransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0)];
CAAnimationGroup * animationGroup = [CAAnimationGroup animation];
animationGroup.duration = 0.25f;
animationGroup.animations = @[extendAnimation, reduceAnimation, revertAnimation];
animationGroup.repeatCount = 1;
return animationGroup;
}
+ (CABasicAnimation *)opacityAnimation
{
CABasicAnimation * opacityAnimation = [CABasicAnimation animationWithKeyPath: @"opacity"];
opacityAnimation.fromValue = @1.0f;
opacityAnimation.toValue = @0.0f;
opacityAnimation.duration = 0.175f;
opacityAnimation.autoreverses = YES;
opacityAnimation.repeatCount = 1;
return opacityAnimation;
}
+ (CABasicAnimation *)scaleAnimation
{
CABasicAnimation * scaleAnimation = [CABasicAnimation animationWithKeyPath: @"transform.scale"];
scaleAnimation.fromValue = [NSValue valueWithCATransform3D: CATransform3DMakeScale(0.0, 0.0, 1.0)];
scaleAnimation.toValue = [NSValue valueWithCATransform3D: CATransform3DMakeScale(1.0, 1.0, 1.0)];
scaleAnimation.duration = 0.35f;
return scaleAnimation;
}
+ (CABasicAnimation *)rotateAnimation
{
CABasicAnimation * rotateAnimation = [CABasicAnimation animationWithKeyPath: @"transform.rotation.z"];
rotateAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2];
rotateAnimation.duration = 0.35f;
rotateAnimation.repeatCount = 1;
return rotateAnimation;
}
+ (CABasicAnimation *)flipAtXAnimation
{
CABasicAnimation * flipAnimation = [CABasicAnimation animationWithKeyPath: @"transform.rotation.y"];
flipAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2];
flipAnimation.duration = 0.35f;
flipAnimation.repeatCount = 1;
return flipAnimation;
}
+ (CABasicAnimation *)flipAtYAnimation
{
CABasicAnimation * moveAnimation = [CABasicAnimation animationWithKeyPath: @"transform.rotation.x"];
moveAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2];
moveAnimation.duration = 0.35f;
moveAnimation.repeatCount = 1;
return moveAnimation;
}
@end</span>