-
分享 获得信息多少的层级分化 大多起源于人为刻意构筑的学习成本 在任何知识领域 零学习成本的知识都是最珍贵的 因为这给任何人在相关专业上一个向上流动的机会 所以保持分享信息的习惯 不论用的是开源协议还是Creative Commons
-
遮掩 自己认为对的事情 怎么做都不需要对别人遮掩 如果有人觉得写博客很矫情 那是他的认为 如果有人觉得看社科很装逼 那也是他的认为
-
武断的评价 聆听别人的话语 不要着急用自己的价值观进行武断的评价 你不需要对任何刚听到的事情都评价 进而获得对自己的肯定 那是自卑的表现 更何况 很多事情并不如你所料
-
利己主义 我并不排斥利己主义 很多别人口中“精致的利己主义者” 其实并没有什么道义上的缺陷 只不过吃相难看了点 话说回来这个社会谁不是利己的 我很喜欢利己主义 毕竟现在我连自己都没能养活 连自己都没能养活的人 说什么不齿利己主义 简直是笑掉大牙
-
眼界 眼界是辅助一个人做判断的关键 什么可能 什么不可能 什么是最有价值的 这些东西都要到一定层度才可以分辨
-
重复 重复工作而没有新知识获得 基本上就是失败的 要么不要做重复的工作 要么做得多样 否则就毫无价值
-
原教旨 在讨论日常生活的一些原教旨情节时 记得思考 是不是因为自称原教旨让你获得了更高贵的感觉 所以才如此 这点 无论是对于小众音乐 冷门电影 还是写代码 都是如此
-
错过 不要一直想着去听其他人在说什么 不要怕因为没有听别人在说什么而漏过错过什么事情 很多人的话语其实信息量很低 不听也罢
tvOS视差按钮的ObjC实现
这是为Animatious 一起动画开源组写的文章 请关注我们!!!
介绍
苹果在最新发布的Apple TV里引入了有趣的图标设计
具体说来 图标由2-5个分层图层构成 在图标被选中的时候 图标内每个图层进行不同幅度的位移 从而形成视觉上具有深度距离感的视差效果 图标构成和效果可以见视频:
原理
假设我们已经有了以下图片:(你可以从下载链接下载已经分层的四张图片)
基于这四张图片 我们该怎么对其进行变换来达到tvOS的视差效果呢?
重新观察上文中苹果官方的例子视频 我们可以得出以下结论:
1. 总图层在旋转 但不同于一般在屏幕平面上的旋转 而是相对屏幕具有一定夹角的旋转
如果不太了解这种旋转是怎么发生的 我们可以看一张有关 CATransform3D 的图:
我们常用的 CGRect 有 X 和 Y 两个位置参数 而 CATransform3D 可以理解为在日常的两个轴以外加了 Z 轴 方向为从手机上表面竖直向下 如图
这么一来 我们日常所见的在屏幕平面上的旋转(比如屏幕旋转)其实是绕 Z 轴旋转
而绕 X 和 Y 轴的旋转 便是让 CALayer 具有相对屏幕具有一定夹角的旋转 具体表现就是如同tvOS按钮一样有远近效果(其实在透视效果上还是有些不一样 后面会提怎么解决)
CATransform3DMakeRotation
就是这么通过旋转角度定义的:
CATransform3D CATransform3DMakeRotation (
CGFloat angle, //绕着向量(x,y,z)旋转的角度
CGFloat x, //x轴分量
CGFloat y, //y轴分量
CGFloat z //z轴分量
);
2.除去旋转 每个图层都在进行不同半径的圆周移动
为什么有移动?
透视是创造三维深度感觉的关键 而透视效果最直白的话来说 就是“近大远小”
让我们来看个例子吧 :-)
如果只有总图层自转 分图层不进行移动 那么整个按钮虽然有自转效果 但是看起来还是平的
如果要保证有三维效果 就要有视差 即近大远小 让远处的图层移动的距离很小 近处的图层移动距离很大(大家可以自行想象同样速度远处近处的汽车 看起来移动的距离也不一样)
因此 就要令分图层进行圆周移动 离我们近的图层 圆周半径要更大些 保证看起来移动的距离更大
我们简单地用 Principle 做了一个原型 大体效果应该是这个样子的 中间的圆点是移动的轴心
3.总的图层也会移动
看到我们刚才那个简陋的效果了没?你有可能会想 为什么看起来和tvOS差别那么大?
原因是 tvOS实现的效果 整个按钮并没有明显地移动 而是近似于固定在某个位置的 这么一来 就要求我们在分图层移动的时候 总图层叠加一项反方向的移动 保证按钮固定住
于是我们又用 Principle 做了一个原型 大体效果应该是这个样子的
4.高光的移动方向恰好相反
高光就是我们在tvOS的图标上看到的白色反光 这个部分其实很简单:
用PS画一个白色的圆 加上模糊效果 就是一个 高光图层
让图层在移动的时候于其他图层方向相反 即让图层叠加之后的效果为 高光永远在离我们最近的地方 这里说起来会有点困惑 但是用代码实现的时候就自然明白了 ^_^
实现
注:实现部分限于篇幅 不可能将所有代码都粘贴出来 只是在几个关键的地方粘贴出来加以说明
完整代码见 https://github.com/JustinFincher/JZtvOSParallaxButton
1.按钮基本
按照我们的计划 这个按钮默认并没有三维效果 就是很多UIImage叠加起来 只有当我们长按的时候 才会有各种旋转和移动
这里动画方式分为两种 第一种是自动动画 首先会移动和旋转到一个特定的角度 然后便开始周期移动了 第二种是手动动画 按钮会跟随手指Drag进行旋转和移动
让我们先新建一个UIButton吧 :-)
//JZParallaxButton.h
# import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, RotateMethodType)
{
AutoRotate = 0, //自动动画
WithFinger, //跟随手指
WithFingerReverse, //跟随手指 但反向
};
@interface JZParallaxButton : UIButton
@end
//JZParallaxButton.m
# define LongPressInterval 0.5 //自动动画状态下的长按判断时间
@interface UIButton ()<UIGestureRecognizerDelegate>
@end
@implementation JZParallaxButton
@end
写一个自己的init方法 然后在里面加入长按的手势判断
//JZParallaxButton.m
- (instancetype)initButtonWithCGRect:(CGRect)RectInfo
WithLayerArray:(NSMutableArray *)ArrayOfLayer
WithRoundCornerEnabled:(BOOL)isRoundCorner
WithCornerRadiusifEnabled:(CGFloat)Radius
WithRotationFrames:(int)Frames
WithRotationInterval:(CGFloat)Interval
{
self = [super initWithFrame:RectInfo];
//...省略...
UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(selfLongPressed:)];
longPress.delegate = self;
longPress.minimumPressDuration = LongPressInterval;
//self就是UIButton了 所以可以对self add
[self addGestureRecognizer:longPress];
return self;
}
//JZParallaxButton.m
//长按会触发的方法
- (void)selfLongPressed:(UILongPressGestureRecognizer *)sender
{
CGPoint PressedPoint = [sender locationInView:self];
NSLog(@"%F , %F",PressedPoint.x,PressedPoint.y);
//可以读取当前按下的位置
}
2.层级关系和逻辑判断
我们的按钮在实现后应有以下层级:
ParallaxButton:UIButton //我们建立的UIButton SubClass
|-BoundsView:UIView //总视图
|--LayerImageView1:UIImageView //分视图 是总视图的SubView
|--LayerImageView2:UIImageView
|--LayerImageView3:UIImageView
|--LayerImageView4:UIImageView
|--LayerImageView5:UIImageView
|-- ....
|--LayerImageViewX:UIImageView
有的同学有可能会觉得 为什么需要总视图这个 BoundsView
呢 直接将所有的UIImageView都划归为UIButton的SubView不就好了么?
实验过直接将UIImageView添加到UIButton为SubView后 我有一个相对合理的解释:
我们之前分析原理的时候 说明其实是只有总图层(即 BoundsView
)在旋转的 分图层只需处理移动问题
如果去除了总图层 就只能让每个分图层(即 LayerImageView
)在移动的同时都旋转 这势必带来一个问题 那就是会有“厚度”的感觉
让我们实验下 如果层级关系如下图 会是什么结果
可以看到 这里的图层移动方式和原型里的效果已经很接近了 但是因为分图层移动半径不一 并且没有总图层进行约束 导致分图层的显示区域不在一个长方形里 看起来像是有厚度了一样 整个按钮实际看起来并没有tvOS按钮里的那种轻盈感
因此 需要有总图层进行约束 即将分图层添加为总图层的SubView 并设置总图层Layer的MasksToBounds为YES 这时 所有分图层的可见区域都受限制于总图层 无论怎么旋转和移动都不会出现厚度感了
我们现在将视图层级需要的一些变量写出来 然后再实现一些逻辑判断的代码 比如长按后需要做什么
//JZParallaxButton.h
# import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, ParallaxMethodType)
{
Linear = 0,
EaseIn,
EaseOut,
};
@interface JZParallaxButton : UIButton
//数组用于记录当前Button包含的所有ImageLayer 即分图层
@property (nonatomic,strong) NSMutableArray *LayerArray;
//button圆角
@property (nonatomic,assign) BOOL RoundCornerEnabled;
//button圆角
@property (nonatomic,assign) CGFloat RoundCornerRadius;
//是否在Parallax
@property (nonatomic,assign) BOOL isParallax;
@property (nonatomic,assign) int RotationAllSteps;
@property (nonatomic,assign) CGFloat RotationInterval;
@property (nonatomic,assign) CGFloat ScaleBase;
@property (nonatomic,assign) CGFloat ScaleAddition;
@property (nonatomic,assign) ParallaxMethodType ParallaxMethod;
@property (nonatomic,assign) RotateMethodType RotateMethod;
- (instancetype)initButtonWithCGRect:(CGRect)RectInfo
WithLayerArray:(NSMutableArray *)ArrayOfLayer
WithRoundCornerEnabled:(BOOL)isRoundCorner
WithCornerRadiusifEnabled:(CGFloat)Radius
WithRotationFrames:(int)Frames
WithRotationInterval:(CGFloat)Interval;
//JZParallaxButton.m
# define RotateParameter 0.5 //用于调整旋转幅度
# define SpotlightOutRange 20.0f //用于高光距离中心的长度
# define zPositionMax 800 //Core Layer变换时摄像机默认z轴位置
# define BoundsVieTranslation 50 //总图层的移动幅度
# define LayerVieTranslation 20 //分图层的移动幅度
# define LongPressInterval 0.5 //自动动画状态下的长按判断时间
@interface UIButton ()<UIGestureRecognizerDelegate>
@property (nonatomic,assign) int RotationNowStep; //记录动画的当前状态
@property (nonatomic,weak)NSTimer *RotationTimer; //动画计时器
@property (nonatomic,strong) UIImageView *SpotLightView; //高光图层
@property (nonatomic,strong) UIView *BoundsView; //总图层
@property (nonatomic,assign) CGPoint TouchPointInSelf; //手指按下的时候在Button内部 的位置 用于Button设为跟随手指的时候
@property (nonatomic,assign) BOOL hasPreformedBeginAnimation; //判断是否在进行动画 防止动画未表演完就触发下一个动作 造成错位
@end
@implementation JZParallaxButton
//省略 @synthesize ...
- (instancetype)initButtonWithCGRect:(CGRect)RectInfo
WithLayerArray:(NSMutableArray *)ArrayOfLayer
WithRoundCornerEnabled:(BOOL)isRoundCorner
WithCornerRadiusifEnabled:(CGFloat)Radius
WithRotationFrames:(int)Frames
WithRotationInterval:(CGFloat)Interval
{
//省略之前的代码....
LayerArray = [[NSMutableArray alloc] initWithCapacity:[ArrayOfLayer count]];
BoundsView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
BoundsView.layer.masksToBounds = YES;
BoundsView.layer.shouldRasterize = TRUE;
BoundsView.layer.rasterizationScale = [[UIScreen mainScreen] scale];
if (self.RoundCornerEnabled)
{
BoundsView.layer.cornerRadius = self.RoundCornerRadius;
}
[self addSubview:BoundsView];
for (int i = 0; i < [ArrayOfLayer count]; i++)
{
UIImageView *LayerImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
UIImage *LayerImage = [ArrayOfLayer objectAtIndex:i];
[LayerImageView setImage:LayerImage];
LayerImageView.layer.shouldRasterize = TRUE;
LayerImageView.layer.rasterizationScale = [[UIScreen mainScreen] scale];
//从下往上添加
[BoundsView addSubview:LayerImageView];
[LayerArray addObject:LayerImageView];
//如果把所有分图层都加完了
if (i == [ArrayOfLayer count] - 1)
{
//在最上层添加高光分图层
SpotLightView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width,self.frame.size.height)];
NSString *bundlePath = [[NSBundle bundleForClass:[JZParallaxButton class]]
pathForResource:@"JZParallaxButton" ofType:@"bundle"];
NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
SpotLightView.image = [UIImage imageNamed:@"Spotlight" inBundle:bundle compatibleWithTraitCollection:nil];
SpotLightView.contentMode = UIViewContentModeScaleAspectFit;
SpotLightView.layer.masksToBounds = YES;
[BoundsView addSubview:SpotLightView];
SpotLightView.layer.zPosition = zPositionMax;
[self bringSubviewToFront:SpotLightView];
SpotLightView.alpha = 0.0;
[LayerArray addObject:SpotLightView];
}
}
- (void)selfLongPressed:(UILongPressGestureRecognizer *)sender
{
CGPoint PressedPoint = [sender locationInView:self];
//NSLog(@"%F , %F",PressedPoint.x,PressedPoint.y);
self.TouchPointInSelf = PressedPoint;
if(sender.state == UIGestureRecognizerStateBegan)
{
//NSLog(@"Long Press");
self.hasPreformedBeginAnimation = NO;
switch (self.RotateMethod)
{
case AutoRotate:
{
//长按后 如果在进行视差效果就结束 如果现在是普通状态就开启视差效果
if (isParallax)
{
[self EndParallax];
}
else
{
[self BeginParallax];
}
}
break;
case WithFinger:
{
//手动动画结束视差效果并不依靠长按 而是通过判断手指是否留在屏幕上
if (!isParallax)
{
[self BeginParallax];
}
}
break;
}
}
else if (sender.state == UIGestureRecognizerStateEnded)
{
//如果长按结束但仍有动画在进行 就等待 LongPressInterval 再执行TouchUp方法 否则立即执行TouchUp
if (self.hasPreformedBeginAnimation == NO)
{
[self performSelector:@selector(TouchUp) withObject:self afterDelay:LongPressInterval ];
}
else
{
[self TouchUp];
}
}
}
@end
这里的 shouldRasterize
:
@property BOOL shouldRasterize
Description A Boolean that indicates whether the layer is rendered as a bitmap before compositing. Animatable
在某些时候 例如导入的图片分辨率较大 此时对UIViewImage进行CA动画 会出现锯齿 这个时候 可以通过设置 shouldRasterize
来解决
BoundsView.layer.shouldRasterize = YES;
BoundsView.layer.rasterizationScale = [[UIScreen mainScreen] scale];
当然设置 allowsEdgeAntialiasing
也是一种方法 这里我们对比下 可以注意看右边墙壁上的树影 通过 shouldRasterize
设置后 基本上影子不会出现较大的变动:
3.透视效果需要实现的一个方法
//JZParallaxButton.m
//CATransform3DMake 默认给出的CATransform3D是没有透视效果的 需要手动加入这一段 完成从 orthographic 到 perspective 的改变
//这个方法的解析可以看 http://wonderffee.github.io/blog/2013/10/19/an-analysis-for-transform-samples-of-calayer/
CATransform3D CATransform3DMakePerspective(CGPoint center, float disZ)
{
CATransform3D transToCenter = CATransform3DMakeTranslation(-center.x, -center.y, 0);
CATransform3D transBack = CATransform3DMakeTranslation(center.x, center.y, 0);
CATransform3D scale = CATransform3DIdentity;
scale.m34 = -1.0f/disZ;
return CATransform3DConcat(CATransform3DConcat(transToCenter, scale), transBack);
}
CATransform3D CATransform3DPerspect(CATransform3D t, CGPoint center, float disZ)
{
return CATransform3DConcat(t, CATransform3DMakePerspective(center, disZ));
}
4.实现自动动画
这部分主要内容就是 在长按后 按钮会先进入某个特定的角度位置 然后进行自转
这里为了简单 使用了NSTimer进行每一帧的计数 但需要注意的是 NSTimer的精度不足以完成真正流畅的动画
自动动画里 总图层和分图层的移动 旋转都和两个参数有关:一是当前的计数位置(即) 而是图层在总按钮里的层级位置(即LayerArray里的i) 通过这两个参数进行计算CATransform3D
// JZParallaxButton.m
#define OutTranslationParameter (float)([LayerArray count] + i)/(float)([LayerArray count] * 2)
#define OutScaleParameter ScaleBase+ScaleAddition/5*((float)i/(float)([LayerArray count]))
@implementation JZParallaxButton
- (void)BeginAutoRotation
{
__weak JZParallaxButton *weakSelf = self;
//需要到达动画的起始位置
CGFloat PIE = 0;
CGFloat Degress = M_PI*2*PIE;
//NSlog(@"Degress : %f PIE",PIE);
CGFloat Sin = sin(Degress)/4;
CGFloat Cos = cos(Degress)/4;
int i =0;
//计算初始位置的旋转 移动 和缩放
CATransform3D NewRotate,NewTranslation,NewScale;
NewRotate = CATransform3DConcat(CATransform3DMakeRotation(-Sin*RotateParameter, 0, 1, 0), CATransform3DMakeRotation(Cos*RotateParameter, 1, 0, 0));
NewTranslation = CATransform3DMakeTranslation(Sin*BoundsVieTranslation*OutTranslationParameter, Cos*BoundsVieTranslation*OutTranslationParameter, 0);
NewScale = CATransform3DMakeScale(OutScaleParameter, OutScaleParameter, 1);
//使用CATransform3DConcat将三个CATransform3D结合成一个CATransform3D
CATransform3D TwoTransform = CATransform3DConcat(NewRotate,NewTranslation);
CATransform3D AllTransform = CATransform3DConcat(TwoTransform,NewScale);
//对BoundsLayer 即总图层进行动画
CABasicAnimation *BoundsLayerCABasicAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
BoundsLayerCABasicAnimation.duration = 0.4f;
BoundsLayerCABasicAnimation.autoreverses = NO;
BoundsLayerCABasicAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DPerspect(AllTransform, CGPointMake(0, 0), zPositionMax)];
BoundsLayerCABasicAnimation.fromValue = [NSValue valueWithCATransform3D:BoundsView.layer.transform];
BoundsLayerCABasicAnimation.fillMode = kCAFillModeBoth;
BoundsLayerCABasicAnimation.removedOnCompletion = YES;
[BoundsView.layer addAnimation:BoundsLayerCABasicAnimation forKey:@"BoundsLayerCABasicAnimation"];
BoundsView.layer.transform = CATransform3DPerspect(AllTransform, CGPointMake(0, 0), zPositionMax);
//对LayerArray内的UIImageView 即分图层进行动画
for (int i = 0 ; i < [LayerArray count]; i++)
{
//对于不同的前后位置 需要移动的半径不一样
float InTranslationParameter = [self InTranslationParameterWithLayerArray:LayerArray WithIndex:i];
float InScaleParameter = [self InScaleParameterWithLayerArray:LayerArray WithIndex:i];
UIImageView *LayerImageView = [LayerArray objectAtIndex:i];
CATransform3D NewTranslation ;
CATransform3D NewScale = CATransform3DMakeScale(InScaleParameter, InScaleParameter, 1);
if (i == [LayerArray count] - 1) //是高光所在的View
{
NewTranslation = CATransform3DMakeTranslation(Sin*LayerVieTranslation*InTranslationParameter*SpotlightOutRange, Cos*LayerVieTranslation*InTranslationParameter*SpotlightOutRange, 0);
}
else //分图层其他的View 注意可以看到这里的 Translation 和高光是相反的
{
NewTranslation = CATransform3DMakeTranslation(-Sin*LayerVieTranslation*InTranslationParameter, -Cos*LayerVieTranslation*InTranslationParameter, 0);
}
CATransform3D NewAllTransform = CATransform3DConcat(NewTranslation,NewScale);
CABasicAnimation *LayerImageViewCABasicAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
LayerImageViewCABasicAnimation.duration = 0.4f;
LayerImageViewCABasicAnimation.autoreverses = NO;
LayerImageViewCABasicAnimation.toValue = [NSValue valueWithCATransform3D:NewAllTransform];
LayerImageViewCABasicAnimation.fromValue = [NSValue valueWithCATransform3D:LayerImageView.layer.transform];
LayerImageViewCABasicAnimation.fillMode = kCAFillModeBoth;
LayerImageViewCABasicAnimation.removedOnCompletion = YES;
CAAnimationGroup *animGroup = [CAAnimationGroup animation];
animGroup.animations = [NSArray arrayWithObjects:LayerImageViewCABasicAnimation,nil];
animGroup.duration = 0.4f;
animGroup.removedOnCompletion = YES;
animGroup.autoreverses = NO;
animGroup.fillMode = kCAFillModeRemoved;
[CATransaction begin];
LayerImageView.layer.transform = CATransform3DPerspect(NewAllTransform, CGPointMake(0, 0), zPositionMax);
[CATransaction setCompletionBlock:^
{
if (i == [LayerArray count] - 1)
{
//简单的周期循环
RotationNowStep = 0;
RotationTimer = [NSTimer scheduledTimerWithTimeInterval:RotationInterval/RotationAllSteps target:weakSelf selector:@selector(RotationCreator) userInfo:nil repeats:YES];
weakSelf.hasPreformedBeginAnimation = YES;
}
}];
[LayerImageView.layer addAnimation:animGroup forKey:@"LayerImageViewParallaxInitAnimation"];
[CATransaction commit];
}
}
//计时器每次触发要执行的方法
- (void)RotationCreator
{
__weak JZParallaxButton *weakSelf = self;
//NSlog(@"RotationNowStep : %d of %d",RotationNowStep,RotationAllSteps);
if (RotationNowStep == RotationAllSteps)
{
//一周完成 计数器置1
RotationNowStep = 1;
}
else
{
RotationNowStep ++ ;
}
CGFloat PIE = (float)RotationNowStep/(float)RotationAllSteps;
CGFloat Degress = M_PI*2*PIE;
//NSlog(@"Degress : %f PIE",PIE);
CGFloat Sin = sin(Degress)/4;
CGFloat Cos = cos(Degress)/4;
int i = 0;
CATransform3D NewRotate,NewTranslation,NewScale;
NewRotate = CATransform3DConcat(CATransform3DMakeRotation(-Sin*RotateParameter, 0, 1, 0), CATransform3DMakeRotation(Cos*RotateParameter, 1, 0, 0));
NewTranslation = CATransform3DMakeTranslation(Sin*BoundsVieTranslation*OutTranslationParameter, Cos*BoundsVieTranslation*OutTranslationParameter, 0);
NewScale = CATransform3DMakeScale(OutScaleParameter, OutScaleParameter, 1);
CATransform3D TwoTransform = CATransform3DConcat(NewRotate,NewTranslation);
CATransform3D AllTransform = CATransform3DConcat(TwoTransform,NewScale);
BoundsView.layer.transform = CATransform3DPerspect(AllTransform, CGPointMake(0, 0), zPositionMax);
for (int i = 0 ; i < [LayerArray count]; i++)
{
float InScaleParameter = [self InScaleParameterWithLayerArray:LayerArray WithIndex:i];
float InTranslationParameter = [self InTranslationParameterWithLayerArray:LayerArray WithIndex:i];
if (i == [LayerArray count] - 1) //is spotlight
{
UIImageView *LayerImageView = [weakSelf.LayerArray objectAtIndex:i];
CATransform3D Translation = CATransform3DMakeTranslation(Sin*LayerVieTranslation*InTranslationParameter*SpotlightOutRange, Cos*LayerVieTranslation*InTranslationParameter*SpotlightOutRange,0);
CATransform3D Scale = CATransform3DMakeScale(InScaleParameter, InScaleParameter, 1);
CATransform3D AllTransform = CATransform3DConcat(Translation,Scale);
LayerImageView.layer.transform = CATransform3DPerspect(AllTransform, CGPointMake(0, 0), zPositionMax);
}
else //is Parallax layer
{
UIImageView *LayerImageView = [weakSelf.LayerArray objectAtIndex:i];
CATransform3D Translation = CATransform3DMakeTranslation(-Sin*LayerVieTranslation*InTranslationParameter, -Cos*LayerVieTranslation*InTranslationParameter, 0);
CATransform3D Scale = CATransform3DMakeScale(InScaleParameter, InScaleParameter, 1);
CATransform3D AllTransform = CATransform3DConcat(Translation,Scale);
LayerImageView.layer.transform = CATransform3DPerspect(AllTransform, CGPointMake(0, 0), zPositionMax);
}
}
}
- (float)InTranslationParameterWithLayerArray:(NSMutableArray *)Array
WithIndex:(int)i
{
switch (ParallaxMethod)
{
//移动半径和图层层级成线性关系
case Linear:
return (float)(i)/(float)([Array count]);
break;
//移动半径和图层层级成二次关系
case EaseIn:
return powf((float)(i)/(float)([Array count]), 0.5f);
break;
//移动半径和图层层级成根号关系
case EaseOut:
return powf((float)(i)/(float)([Array count]), 2.0f);
break;
default:
return (float)(i)/(float)([Array count]);
break;
}
}
- (float)InScaleParameterWithLayerArray:(NSMutableArray *)Array
WithIndex:(int)i
{
switch (ParallaxMethod)
{
//缩放与图层层级的不同关系
case Linear:
return 1+ScaleAddition/10*((float)i/(float)([LayerArray count]));
break;
case EaseIn:
return 1+ScaleAddition/10*powf(((float)i/(float)([LayerArray count])), 0.5f);
break;
case EaseOut:
return 1+ScaleAddition/10*powf(((float)i/(float)([LayerArray count])), 2.0f);
break;
default:
return 1+ScaleAddition/10*((float)i/(float)([LayerArray count]));
break;
}
}
@end
4.实现手动动画
//手动动画和自动动画的区别是 移动的角度不再跟进计数器计算 而是直接读取手指的CGPoint
__weak JZParallaxButton *weakSelf = self;
CGFloat XOffest;
if (TouchPointInSelf.x < 0)
{
XOffest = - weakSelf.frame.size.width / 2;
}else if (TouchPointInSelf.x > weakSelf.frame.size.width)
{
XOffest = weakSelf.frame.size.width / 2;
}else
{
XOffest = TouchPointInSelf.x - weakSelf.frame.size.width / 2;
}
CGFloat YOffest;
if (TouchPointInSelf.y < 0)
{
YOffest = - weakSelf.frame.size.height / 2;
}else if (TouchPointInSelf.y > weakSelf.frame.size.height)
{
YOffest = weakSelf.frame.size.height / 2;
}else
{
YOffest = TouchPointInSelf.y - weakSelf.frame.size.height / 2;
}
//NSLog(@"XOffest : %f , YOffest : %f",XOffest,YOffest);
CGFloat XDegress = XOffest / weakSelf.frame.size.width / 2;
CGFloat YDegress = YOffest / weakSelf.frame.size.height / 2;
//NSLog(@"XDegress : %f , YDegress : %f",XDegress,YDegress);
效果
此时还有很多方法没有实现(比如动画结束后需要变回原有的非三维效果) 不过大体上已经可以看到效果了 你也可以直接将完成版的视差按钮下载下来
Have Fun :-)
其他资源
如果你对真正三维状态下的按钮还是不太理解 可以点击下面的播放按钮自由查看这个三维模型 点击播放按钮后 通过三维场景右下角的左右切换查看按钮的不同旋转状态 或者点击数字1-5来快速跳转
以外 这篇文章里所有文件都是提供公共下载的
配图所用Sketch文件:下载链接
三维模型文件(进入点击Download):下载链接
DROP.
什么时候 能够把AE模版里面那种Motion Graphic 以及Behance上各种近未来HUD的那种精髓 —— 简练 速度 力量 带到软件动效中去 相信使用软件也能够感受到类似于看科幻电影般的快感吧
Cocoapods新建pod教程
介绍
首先 庆祝我有了第一个Pod ^_^
名字叫 JZMultiChoicesCircleButton
是一个三维的多选择按钮
链接:CocoaControls / Github / Dribbble
安装
(以下所有介绍 有加粗的内容是需要替换为你自己的)
gem install cocoapods
我目前使用的是0.39.0版本 你可以通过执行
pod --version
查看你的版本
总览
1.写好你的库 划分好你需要打包哪些东西 图片素材 音频素材 等等
2.使用 pod lib
命令开启一个Pod模板 然后更新模板说明和版本号
3.修改模板内容
4.发布到Github以及其它地方
划分
比如说我现在写了一个叫做 JZMultiChoicesCircleButton
的UIView
主要内容有:
JZMultiChoicesCircleButton.h
JZMultiChoicesCircleButton.m
CallbackWrong.png
CallbackSuccess.png
可以看到 这就是我需要打包在Pod里面的所有内容了 把他们的位置都记录好 等待马上迁移至Pod里面
创建模板
打开Terminal cd
到一个你认为合适的位置 执行
pod lib create JZMultiChoicesCircleButton
(JZMultiChoicesCircleButton 请替换为你自己的Pod名称)
Terminal会询问以下几个事项:
编程语言:ObjC / Swift
是否包含Demo程序: 是 / 否
有没有用测试框架: 有 / 没有
Prefix是什么: 空 / 你的Prefix
填好以上内容 Cocoapods 会打开一个Xcode工程文件
(如果选择了“包含Demo程序” 新建Pod的工程文件时 会在JZMultiChoicesCircleButton本身的Target之外新建一个JZMultiChoicesCircleButton_Demo的Target 用于部署Demo)
修改模板
需要修改的有:
.podspec:描述Pod的内容
README:用于在Github上展示
LICENSE:默认MIT
从Xcode中查看这个路径下:
JZMultiChoicesCircleButton/Podspec Metadata/JZMultiChoicesCircleButton.podspec
进行修改**.podspec** 具体修改内容为
s.name = "JZMultiChoicesCircleButton"
s.version = "0.1.0"
s.summary = "Multi-choices button with 3D parallax effect."
name:Pod的名称
version:版本号
summary:一个简介
s.homepage = "https://github.com/JustinFincher/JZMultiChoicesCircleButton"
s.license = 'MIT'
s.author = { "Fincher Justin" => "zhtsu47@me.com" }
s.source = { :git => "https://github.com/JustinFincher/JZMultiChoicesCircleButton.git", :tag => s.version.to_s }
s.social_media_url = 'http://fincher.im/'
homepage:Github项目地址 如果还没创立可以先去创建一个
license:许可证选择
author:姓名和邮箱
source:Github 附有版本号的tag的clone地址
social_media_url:默认推特地址
s.platform = :ios, '7.0'
s.requires_arc = true
s.source_files = 'Pod/Classes/**/*'
s.resource_bundles = {
'JZMultiChoicesCircleButton' => ['Pod/Assets/*.png']
}
platform:最低运行系统版本
requires_arc:ARC或者MRC
source_files:文件目录内的源文件地址
resource_bundles:资源文件地址 在我这里是指 Assets文件夹里面所有的png文件 并且bundle名称和Pod同名
从Xcode中查看这个路径下:
JZMultiChoicesCircleButton/Podspec Metadata/README.md
写好你的介绍 用法 以及一些其他的东西
添加内容
从Xcode中查看这个路径下:
Pods/Development Pods/JZMultiChoicesCircleButton/Pod
里面有文件夹
Classes
里面有个叫ReplaceMe.m
的文件 删除即可
新建一个文件夹在Pod层级,名字叫Assets好了...现在Pod里面有两个文件夹
Assets
Classes
把**.h和.m**文件拖入Classes里面
把图片文件拖入Assets里面 不要用.xcassets.... 直接拖入@1x @2x @3x的png就好 如图:
好 这个时候 Pod本身就完工了
第一次Push
为什么现在就要Push?
原因是后面我们要添加Demo 而这个时候我们的Pod还没有发布到Github 没有办法进行git clone
也就没办法pod install
这个时候 我们的Demo是没办法导入头文件的
让我们先验证下我们的**.podspec**有没有写完整
pod lib lint JZMultiChoicesCircleButton.podspec
(JZMultiChoicesCircleButton.podspec 替换为自己的podspec)
按照提示修改 直到没有问题 有个需要注意的是**.podspec**默认的社交网址是推特 这个在国内进行验证的话需要保证Terminal能够访问到 否则会出现验证错误
现在就可以Push了
git add .
git commit -m “Initial Commit"
git remote add origin https://github.com/JustinFincher/JZMultiChoicesCircleButton.git
git push -u origin master
(git remote add origin 替换为你自己的Github仓库地址)
添加Demo
如果在之前的Create命令中选择了创建Demo 那么就可以添加了
首先 需要进入demo文件目录执行 Pod install
> cd Example
> pod install
Analyzing dependencies
Fetching podspec for `JZMultiChoicesCircleButton` from `../`
Downloading dependencies
Installing JZMultiChoicesCircleButton 0.1.0 (was 0.1.0)
Generating Pods project
Integrating client project
从Xcode中查看这个路径下:
JZMultiChoicesCircleButton/Example for JZMultiChoicesCircleButton
这就是你的Demo目录 现在可以导入头文件 写下你的示范代码了
需要注意的一点:
这个时候 如果你写的库中使用图片用的是[UIImage imageNamed:@"xxx"]的话 你有可能发现图片不会显示出来
需要修改你的库 将其中的
CallbackIcon.image = [UIImage imageNamed:@"xxx"]
修改为
NSString *bundlePath = [[NSBundle bundleForClass:[JZMultiChoicesCircleButton class]]
pathForResource:@"JZMultiChoicesCircleButton" ofType:@"bundle"];
NSBundle *bundle = [NSBundle bundleWithPath:bundlePath];
CallbackIcon.image = [UIImage imageNamed:@"xxx" inBundle:bundle compatibleWithTraitCollection:nil];
(修改JZMultiChoicesCircleButton 为你自己的Class和resource_bundles 名称)
也就是说 任何资源文件 无论xib png jpg 都要制定bundle为Pod的resource_bundles 有关resource_bundles具体在之前的podspec中设置
s.resource_bundles = {
'JZMultiChoicesCircleButton' => ['Pod/Assets/*.png']
}
第二次Push
首先 全部推至Github
然后加个tag
> git tag 0.1.0
> git push origin 0.1.0
Total 0 (delta 0), reused 0 (delta 0)
To https://github.com/JustinFincher/JZMultiChoicesCircleButton.git
* [new tag] 0.1.0 -> 0.1.0
验证是否一切完成
pod spec lint JZMultiChoicesCircleButton.podspec
出现
> pod spec lint JZMultiChoicesCircleButton.podspec
-> JZMultiChoicesCircleButton (0.1.0)
Analyzed 1 podspec.
JZMultiChoicesCircleButton.podspec passed validation.
注册一个Trunk的session
pod trunk register zhtsu47@me.com 'Justin Fincher' --description='Macbook Pro 13 2015'
推送至Cocoapods
pod trunk push JZMultiChoicesCircleButton.podspec
COOL!你现在有了一个自己的Pod~
接下来
- 在 https://cocoapods.org/ 搜索你的Pod
- 发布到 https://www.cocoacontrols.com
- 求Star
参考
1.http://code.tutsplus.com/tutorials/creating-your-first-cocoapod--cms-24332
2.http://stackoverflow.com/questions/25817738/access-resources-in-pod
3.http://guides.cocoapods.org/making/getting-setup-with-trunk.html
懒癌
从北京回来一个星期了 现在还是什么都不想做
对于我来说 北京的几次经历让我明白另一种可能性 一种自己想过的生活 完全自由 事情也容易很多的生活
具体说来 不用去上自己不想上的课 想学什么就去学什么 有大把的时间不用浪费在毫无意义的事物上 周围的人和事物有更高的期望和估值 但并不因此显得浮夸 因为有真正专业的人在操作
Um...每次遇到这种情况 我的效率就会彻底为0 也可以说是懒吧
无题
“当我刚开始使用手榴弹的时候我相信很多事情 后来我只相信手榴弹”
SO WHAT'S NEXT?
~~和陈叔聊天 说到我们学校软件创新比赛 还没有demo 各队队员就开始各自自称CEO/COO/CDO等等 我简称他们为CXO Kit
后来又谈到 八九月份有想过成立NodeX外包公司 做房地产方向的手机软件 但是后面经济不好 房地产也跟着没钱 整个事情一下子变得遥遥无期
的确挂靠学校孵化器是可以 但我并不想让NodeX停留在那个地步 没有几个东西能够从学校的孵化器真正的走出来 因为学校的孵化器没有风险 也就没有利益 而我想要NodeX 即使是个小皮包公司(也只能是) 都要保持每一个事情都是看向钱
今年的SegmentFault Hackathon 我们做的东西 和我五月份提交给学校App大赛的东西 在技术上是一回事 因为用的是一个AR SDK 但结果很不一样 而且我能想象得到如果是Hackathon的作品提交给App大赛的评委 结果只会更糟 因为那种水平的比赛 评委没有一个人用Mac 自然就没有拆装的需求 但在Hackathon的现场 我看到90%的电脑都是Macbook ifixitAR的目标人群是这些人 而且可以延伸到更多的拆装教学需求里面
很多事情 或许到了更高的视角 才能确认之前的决定是否是对的 ~~
About Orbital Epoch.
English Ver.
(sorry for my poor english)
I had posted my in-prototype-state space sim game to indieDB :-)
For now the game has the name "orbital-epoch", I may change it later as long as I think it is no longer cool.
It would take 1-2 years to make it, maybe more, considering modeling (all spaceships, stations, cities on planets will be modeled by me), texturing (UV, PBR stuff), Game Logic (for now I don't code, I use blueprint), Model Rigging, Sounds ,Animations and others will take lots of time.
For now the game's goal is making a game feels like the old "freelancer" game (free space, land on planets through a gate, combat with super big ships), on PC/Mac/iOS/VR platforms. More features will be added if I figured out how procedural generation works.
I wish I would get much time as I can, because there are college homework, out-source jobs ( else I won't have money to buy a workstation) and other things to do.
Orbital Epoch 会在我有时间的时候尽力去做 当然目前还需要解决一些问题 比如我要再接一份外包 然后用挣的钱去买台工作站 (MBP无论建模还是贴图都很吃力)然后我才能开始主要的开发工作
以上
消耗成本的进化
如果说 真的保证什么数字消费品都需要正版 那么会发生什么呢?
可以预见的是 学校机房的大部分软件都将换一番 甚至使用Linux也不足为奇 那样的话 倒是比较有趣了 国内的网游厂商 或许会比软件厂商 更快地发行Linux版 谁会和钱过不去呢
另外一个就是 对于家里买电脑都比较困难的家庭来说 想要再去买那些软件 更是困难
虽然 很多大神都是从电脑城几块钱一张的Adobe盗版成长起来的 我不知道这个是国内的特例 还是说 的确有这么一个情况:
(在商业软件比自由软件在某个行业中间应用更主流的情况下)
整体购买力无法满足Copyright的国家里 有Pirate and Crack这么一个不受主流支持的渠道 让某些家庭的孩子们能够得到 更高层级家庭才能够获得的软件条件 因此获得了在软件技术上赶超的可能性 而如果没有这么一个渠道 或许就不会有从五块钱盗版碟成长出来的大神们
这算是什么呢?这可以说是一种隐形的 信息获取的阶层分级和阶级流动了 但流动的方式却没有公开的窗口 而是通过盗版碟完成的?
扯远点,
盗版是不受主流支持的 不过究竟是因为正版获得的利益让正版成之为主流 还是道义上的正确使之?
从盗版成长起来的大神们 工作的内容 却又是极其注重Copyright的(毕竟使用行业软件的目的就是赚钱) 这算是成功地进行了阶层流动 还是一种对原有环境的背叛?
Renewals of complete faith
前几天又看了一遍了不起的盖茨比电影版 才发现一个我一直以来没有注意的情节
Gatsby 在之前和Nick 讲述他的身世之时 说自己世世代代去牛津上学 然后战争爆发 才去参军
但在后来Tom 和Gatsby 在Plaza Hotel里面一番争论 这时候Gatsby 变了说辞 说他去牛津是战争后提供给军官的学习机会 -- 意思是是先参加了战争才去了牛津 所以让Tom 更加怀疑也不足为奇
而我之前 不论读原著 还是看电影 到这里都没有感到任何异常 不是健忘 忘掉了之前盖茨比对尼克的自述 而是感觉逻辑并不违和 -- 就如同Nick 在当时对Gatsby 的感受一样:
Tom glanced around to see if we mirrored his unbelief.
But we were all looking at Gatsby.
‘It was an opportunity they gave to some of the officers
after the Armistice,’ he continued. ‘We could go to any of the universities in England or France.’
I wanted to get up and slap him on the back. I had one of those renewals of complete faith in him that I’d experienced before.
I had one of those renewals of complete faith in him that I’d experienced before.