重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
动画在UI交互中是一种增强用户体验的利器,目前看到几乎每一个移动App都会使用到各种动画效果。
我们一直强调成都做网站、网站建设对于企业的重要性,如果您也觉得重要,那么就需要我们慎重对待,选择一个安全靠谱的网站建设公司,企业网站我们建议是要么不做,要么就做好,让网站能真正成为企业发展过程中的有力推手。专业网站制作公司不一定是大公司,成都创新互联作为专业的网络公司选择我们就是放心。
在IOS开发中实现动画效果通常有三种方式。
可以用来做什么呢:
设置UIView的属性:例如
frame
bounds
center
transform
alpha
backgroundColor
contentStretch
看一下实例:
我们可以看到self.greenView通过UIView Animation动画将某些属性进行了改变。
现在我们适当的加入一些动画执行的方式【options】
在开发中可以添加特定的options满足不同的动画需要。
弹簧效果:
CABasicAnimation 为layer属性提供了基础的帧动画能力,创建一个CABasicAnimation的实例,使用继承自CAPropertyAnimation的animationWithKeyPath:方法,来指定要添加动画的layer属性的keypath
让一个view向左平移,在x方向上从屏幕x中间线型移动到左边消失,耗时1.5秒的动画
KeyPath的改变动画的效果就不一样,开发中改变KeyPath的属性可以实现大多数我们需要的动画执行的效果
用CABasicAnimation执行动画,在动画结束后会回归动画开始前的状态。想要解决的话,必须设置“removedOnCompletion”和“fillMode”这两个属性。
由于在开发过程中光是CABasicAnimation的fromValue、toValue起点和终点设置是无法满足我们希望在动画中途进行更多的变化的需求,所以我们需要认识一下CAKeyframeAnimation
从上面的继承图我们看出CAKeyframeAnimation 比CABasicAnimation多了更多的可设置属性
关键帧动画其实通过一组动画类型的值(或者一个指定的路径)和这些值对应的时间节点以及各时间节点的过渡方式来控制显示的动画。关键帧动画可以通过path属性和values属性来设置动画的关键帧。
可以保存一组动画CAKeyframeAnimation、CABasicAnimation对象,将CAAnimationGroup对象加入图层后,组中所有动画对象可以同时并发运行。
注意:默认情况下,一组动画对象是同时运行的,也可以通过设置单个动画对象的beginTime属性来更改动画的开始时间,单个动画的执行效果可以与动画组执行效果属性分开设定,根据需要调整改变。
iOS开发中,会要求导入@1x、@2x和@3x:
使用@1x格式: iPhone3GS
使用@2x格式: iPhone 4,4S,5,5S,5C,SE,6,6S,7,8,XR
使用@3x格式: iPhone 6Plus、6sPlus、7Plus、8Plus、X、XS、XS Max
这样在开发过程中,将三种图片(比如分别为1.png、1@2x.png和1@3x.png)导入到工程图库中的时候可以自动被识别为1x、2x和3x大小的图片
可以利用Mac系统中自带的服务自己 制作一个快速生成@1x、@2x和@3x图片的功能
首先spotlight搜索Automator,然后按Enter打开
**新建文稿 **
选取文稿类型为 快速操作
在工作流程收到当前的后面选择图像文件
在左边窗口的“操作”下,选择“资源库”中的“文件和文件夹”,将右侧中的“给访达项目重新命名”拖入最右侧的大窗口中,(如果警告提示是否要增加一个“拷贝访达项目”操作,选择“不添加”),选择“添加文本”,在输入框中输入【@3x】
拖入“复制访达项目”
选择左侧“资源库”中的照片,将“缩放图像”拖入右侧窗口(如果警告提示是否要增加一个“拷贝访达项目”操作,选择“不添加”),并选择“按百分比”,输入【66】
再拖入“文件和文件夹”下的“给访达项目重新命名”,并选择【替换文本】,查找【“】,以【仅基本名称】;****再拖入“文件和文件夹”下的“给访达项目重新命名”,并选择【替换文本】,查找【@3x”的副本】,以【仅基本名称】,替换成【@2x】
拖入“复制访达项目”,选择左侧“资源库”中的照片,将“缩放图像”拖入右侧窗口(如果警告提示是否要增加一个“拷贝访达项目”操作,选择“不添加”),并选择“按百分比”,输入【50】
再拖入“文件和文件夹”下的“给访达项目重新命名”,并选择【替换文本】,查找【“】,以【仅基本名称】,再拖入“文件和文件夹”下的“给访达项目重新命名”,并选择【替换文本】,查找【@2x”的副本】,以【仅基本名称】
然后保存,将“快速操作”存储为“制作@2x@3x图片”
每次使用的时候,只需选中图片,选择访达 - 服务 - 制作@2x@3x图片,****就会自动生成三个图片:1.png、1@2x.png和1@3x.png
最终效果~
1、SliderNavigation拥有三个子视图:leftView,rightView,mainView。左右滑动时就通过这三个视图之间层次关系的切换来实现。
2、其实只有上述三个视图完全够了,但是又另外加上了三个属性:leftVC,rightVC,mainVC。这样做的目的是简化操作,同时mainVC还有记录已展示过的视图的任务,这样所有视图都可以通过左右滑动唤出导航栏来了。这样每个子视图上展示的是对应控制器的视图,即[leftView addSubview:leftVC.view];,其他类似。
3、当向左滑动时,调整视图层级关系,因为向左滑动是展示右视图,所以将leftView调整到最底层,同时让mainView随手指移动,这样mainView之下的rightView就展示出来了。
4、有了上述三点,接下来就可以通过给各个环节添加动画来实现好看的效果了。
三、接口定义
.h文件中定义好外界可以自定义的一些属性。
首先是三个控制器
?
1
2
3
4
//左右控制器与主控制器
@property (strong, nonatomic) UIViewController *leftController;
@property (strong, nonatomic) UIViewController *rightController;
@property (strong, nonatomic) UIViewController *mainController;
其次是左右视图的一些相关设定,有判断点、便宜量、动画时间、能否被拉出等
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//左右视图被拉出以后主视图的X方向的offset(正值)
@property (assign, nonatomic) CGFloat leftOffsetX;
@property (assign, nonatomic) CGFloat rightOffsetX;
//左右视图被拉的过程中的判断点的X值(正值)
@property (assign, nonatomic) CGFloat leftJudgeX;
@property (assign, nonatomic) CGFloat rightJudegX;
//左右视图拉出所用的时间
@property (assign, nonatomic) NSTimeInterval leftOpenDuration;
@property (assign, nonatomic) NSTimeInterval rightOpenDuration;
//左右视图收回时所用的时间
@property (assign, nonatomic) NSTimeInterval leftCloseDuration;
@property (assign, nonatomic) NSTimeInterval rightCloseDuration;
//左右视图被拉出以后主视图放缩的比例(0到1)
@property (assign, nonatomic) CGFloat rightScale;
@property (assign, nonatomic) CGFloat leftScale;
//左右视图能否被拉出
@property (assign, nonatomic) BOOL canShowRight;
@property (assign, nonatomic) BOOL canShowLeft;
刚才也说过,mainVC要记下已经展示过的主视图,可以将这些加入到字典中,这样做的作用是下次可以方便的展示出来。另外,让每一个想展示的视图对应的控制器赋值给mainVC可以实现在所有界面中都能通过左右拉来叫出导航栏的功能。什么意思呢?最根部的依旧是我们封装的sliderNavigation类,其上图的层次依旧存在,只是改变了mainVC的值,这样给用户的体验就是,虽然主界面变了,但依然可以拉出左右导航栏来。
为此我们设置一个字典来保存已经展示过的控制器
?
1
2
//用以记录被当做主控制器展示主视图过的控制器
@property (strong, nonatomic) NSMutableDictionary *controllersDict;
接下来是几个public方法声明,将这种Manager性质的类作为单例,暴露出其展示左右视图的功能供按钮控制,然后是可以让其展示自定义类作为主界面。
?
1
2
3
4
5
6
7
8
9
//单例
+ (id)sharedInstance;
//展示左右视图
- (void)showLeftView;
- (void)showRightView;
//展示自定义类的主视图,参数:自定义类名
- (void)showContentViewWithModel:(NSString *)className;
四、具体实现
首先定义一些常量
?
1
2
3
4
5
6
7
8
9
//制造反弹的动态效果,当通过按钮叫出导航栏时有效
static const CGFloat kOpenSpringDamping = 0.65f;
static const CGFloat kOpenSpringVelocity = 0.10f;
//定义常量表示拉动方向
typedef NS_ENUM(NSUInteger, sliderMoveDirection) {
SliderMoveDirectionLeft = 0,
SliderMoveDirectionRight,
};
然后重点这里讲一下关键代码或方法,其余的讲一下思路
我们可以在初始化方法中将接口中声明的变量赋默认值,当用户没有为这些值赋值时便可以用这些默认值
首先我们初始化三个子视图为屏幕大小并根据添加到sliderNavigation的子视图中,注意添加顺序:我们希望让主视图在最上方,所以前两个随意,主视图必须最后添加。
?
1
2
3
4
5
6
7
8
9
10
11
12
- (void)_initSubviews
{
_rightView = [[UIView alloc] initWithFrame:self.view.bounds];
[self.view insertSubview:_rightView atIndex:0];
_leftView = [[UIView alloc] initWithFrame:self.view.bounds];
[self.view insertSubview:_leftView atIndex:1];
//主视图要最后添加(即添加到最上面显示)
_mainView = [[UIView alloc] initWithFrame:self.view.bounds];
[self.view insertSubview:_mainView aboveSubview:_leftView];
}
然后我们初始化左右控制器,将左右控制器视图分别添加到左右视图中去。
在实现上述public方法“展示自定义类的主视图”时,传入参数为类名,将其作为键来从字典中取控制器,如果没有则以此类名新建一个控制器并加入到字典中。如果当前主视图上已经有视图,则将其移除。接着将自定义类的视图添加到mainView上,并相应赋值。
当然,不要忘了关闭左右导航栏(因为展示的类有可能是通过左右导航栏点出来的)
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)showContentViewWithModel:(NSString *)className
{
[self _closeSliderNavigation];
UIViewController *controller = [self.controllersDict objectForKey:className];
if (controller == nil) {
Class c = NSClassFromString(className);
controller = [[c alloc] init];
[self.controllersDict setObject:controller forKey:className];
}
//如果当前已经有视图被显示,则将其取消
if (_mainView.subviews.count 0) {
[[_mainView.subviews firstObject] removeFromSuperview];
}
controller.view.frame = _mainView.frame;
[_mainView addSubview:controller.view];
self.mainController = controller;
}
接着是动画,这里用到的动画主要就是改变视图的大小和位置,用transform即可。获得transform的方法单独抽出来,使用concat将大小变换矩阵和位置变换矩阵连接。接着在动画块中改变主视图的transform即可,当然了,也可以设置上阴影效果等。需要注意的是要根据滑动方向将相应视图调整到最底层。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CGAffineTransform concat = [self _transformWithMoveDirection:SliderMoveDirectionLeft];
[self.view sendSubviewToBack:_leftView];span style="white-space:pre" /span //将另一个视图调到最下面
[self _configureViewShadowWithDirection:SliderMoveDirectionLeft]; //设置阴影
[UIView animateWithDuration:self.rightOpenDuration
delay:0
usingSpringWithDamping:kOpenSpringDampingspan style="white-space:pre" /span //弹性效果
initialSpringVelocity:kOpenSpringVelocity
options:UIViewAnimationOptionCurveLinear
animations:^{
_mainView.transform = concat;
}
completion:^(BOOL finished) {
_showingLeft = NO;
_showingRight = YES;
self.mainController.view.userInteractionEnabled = NO;
_tapGesture.enabled = YES;
}];
另一方向的雷同
最主要的还是滑动手势操作,也是比较麻烦的地方。不过其实思路比较清晰:获取偏移量,在滑动时计算出对应的变换矩阵并设置,在滑动结束时根据位置与判断点的关系做出相应的动画调整。
例如,滑动过程中向右拉时:
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CGFloat translateX = [recognizer translationInView:_mainView].x;
translateX += currentOffsetX;
float scale = 0;
//向右拉,展示的是左视图
if (translateX 0) {
if (self.canShowLeft == NO || self.leftController == nil) {
return;
}
//将右视图放到底部以将左视图显示出来
[self.view sendSubviewToBack:_rightView];
[self _configureViewShadowWithDirection:SliderMoveDirectionRight];
if (_mainView.frame.origin.x self.leftOffsetX) {
scale = 1 - (_mainView.frame.origin.x / self.leftOffsetX) * (1 - self.leftScale);
} else {
scale = self.leftScale;
}
} else if (translateX 0) {……}
比较头痛的十scale的计算。这里的要求是当view从最初到最末时scale的变化为1.0到self.leftScale,因此利用数学知识推出这个公式即可。上述代码省略了向左拉的代码。
而在拉动结束状态则与左拉右拉动画实现类似。
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CGFloat translateX = [recognizer translationInView:_mainView].x;
translateX += currentOffsetX;
if (translateX self.leftJudgeX) {
if (self.canShowLeft == NO || self.leftController == nil) {
return;
}
CGAffineTransform trans = [self _transformWithMoveDirection:SliderMoveDirectionRight];
[UIView beginAnimations:nil context:nil];
_mainView.transform = trans;
[UIView commitAnimations];
_showingLeft = YES;
_showingRight = NO;
self.mainController.view.userInteractionEnabled = NO;
_tapGesture.enabled = YES;
} else if (translateX -self.rightJudgeX) {……}
1.定义函数时,希望传入的参数不为nil,或者为nil时会有警告显示:
如下:
- (instanceype)initWithFoo:(NSString *)foo bar:(NSString *)bar sark:(NSString *)sark
__atttribute__((nonnull(1, 2)));
p.s. index从1开始,不是0,且只对对象生效。
该技巧同样来自,sunnyxx大神的微信,逗比狗~
2.定义函数时,希望子类override该方法时候,必须调用super,否则编译器直接报错。
@interface OldSix:NSObject
- (void)fooWithNothing __attribute__((objc_requires_super));
该技巧同样来自,sunnyxx大神的微信,逗比狗~
wait~~~~~
/*检查子类在重写该方法时有没有调用自己(父类)的实现
*对于一些可以被继承的类,需要子类在重某一调用父类的实现以保证正确的行为,通过在头文件方法的声明末尾添加`NS_REQUIRES_SUPER`
*可以让编译器检查子类方法有调用父类的实现。具体使用方法如下:
* - (void)viewWillAppear:(BOOL)animated NS_REQUIRES_SUPER;
*/
但是根据我的实际测试,发现sunnyxx大神仙的方法,没有任何显示效果,不知道是不是我打开的方式不对还是怎么,
总之,NS_REQUIRES_SUPER的方法亲测可用。
3.误删系统sdk头文件的解决办法
在终端中输入:
$ cd ~/Library/Developer/Xcode/DerivedData/ModuleCache/
$ rm -rf *
4.ARC下打印retainCount
extern uintptr_t _objc_rootRetainCount(id obj);
id test = [NSString new];
NSLog(@"retain count %lu",_objc_rootRetainCount(test));
5.工程报错下,仍然可以编译成功
6.定位方法的调用者
请移步code4app的 找到iOS代码的视图切换(View Transition)标签 有你想要的答案