动画特效十一:侧边栏效果

好久没有进行 "动画特效" 这一系列了。今天和大家继续分享动画特效:“侧边栏效果”。由于侧边栏效果的动画效果各式各样了,所以我大致分为三种进行说明;

样式一:

这个样式的动画效果还是比较容易处理的。主要是通过计算view的宽度或者位置来实现。我这样说,也许有点模糊了。让我们来分析程序的架构模式。

默认情况下是"图一"模式:

1.  "Screen area" 就是移动设备屏幕显示区域。并且它的根控制器就是 "ContainerViewController"。

2.  SliderViewController是 ContainerViewController的子控制器,它的坐标的x,y均为0,高度为屏幕的高度,有自己的宽度。

3. CenterViewController是 ContainerViewController的子控制器,它的坐标的x值就是SliderViewController的View的宽度,y为0,宽高大小等于屏幕的宽高大小。

代码设计思路:

就以点击白色按钮的效果进行说明(注:实际应用中,又将CenterViewController进行导航栏一层的封装,而上面的白色按钮是导航栏的leftBarButtonItem)。

当你点击白色按钮的时候,就来判断当前CenterViewController所在的导航控制器的View的x值。

1. 如果等于0(动画效果就是要从"图二" 变成 "图一");

2. 如果等于侧边栏的宽度(动画效果就是要从"图一" 变成 "图二");

不管动画是从谁执行到谁,均是同时改变两个子控制器的View的x值来实现的。

核心代码如下:

- (void)toggleSideMenu {
    CGFloat ratio = self.centerNavVC.view.x / self.baseWidth;
    BOOL isOpen = ratio == 1.0;
    CGFloat toggleProgress = isOpen ? 0.0 : 1.0;
    [UIView animateWithDuration:0.25 animations:^{
        [self setToPercent:toggleProgress];
    }];
}

- (void)setToPercent:(CGFloat)progress {
    self.slideMenuVC.view.x = (progress - 1) * self.baseWidth;
    self.centerNavVC.view.x = progress * self.baseWidth;
}

注:由于这样的侧边栏效果相对而言比较简单,我并没有用代码进行详细的讲解;后面的两个侧边栏效果我会结合代码进行比较详细的说明。

样式二:

首先,我们来分析程序的架构模式。如下图:

同样的道理,SliderViewController和CenterViewController均是ContainerViewController的子控制器。不同的是先将SliderViewController的View添加到ContainerViewController的View上面,再将CenterViewController的View添加到ContainerViewController的View上面,然后在点击白色按钮或者手势拖拽的时候,SliderViewController的View逐渐变大并且向左移动,直到某个位置的时候,SliderViewController的View
"满高度"显示(即SliderViewController的View的高度等于屏幕的高度),而CenterViewController的x进行一定距离的移动。

根控制器ContainerViewController 代码分析:

1. ViewDidLoad进行初始化工作,代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
    CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;

    // Slide VC
    self.slideMenuVC = [[SlideMenuViewController alloc] init];
    self.slideMenuVC.view.frame = CGRectMake(0, 0, kSliderViewWidth, screenHeight);
    self.cover = [[UIView alloc] init];
    self.cover.frame = self.slideMenuVC.view.bounds;
    self.cover.backgroundColor = [UIColor blackColor];
    self.cover.alpha = kDefaultCoverAlpha;
    [self.slideMenuVC.view addSubview:self.cover];

    [self addChildViewController:self.slideMenuVC];
    [self.view addSubview:self.slideMenuVC.view];
    self.slideMenuVC.delegate = self;
    self.slideMenuVC.centerNavViewController = self.centerNavVC;
    CGAffineTransform scaleTransform = CGAffineTransformMakeScale(kDefaultScale, kDefaultScale);
    CGAffineTransform translationTransform = CGAffineTransformMakeTranslation(kDefaultTranslation, 0);
    self.slideMenuVC.view.transform = CGAffineTransformConcat(scaleTransform, translationTransform);

    // Center VC
    CenterViewController *centerVC = [[CenterViewController alloc] init];
    self.centerNavVC = [[UINavigationController alloc] initWithRootViewController:centerVC];
    self.centerNavVC.view.frame = CGRectMake(0, 0, screenWidth, screenHeight);
    [self addChildViewController:self.centerNavVC];
    [self.view addSubview:self.centerNavVC.view];

    // Gesture
    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
    [self.view addGestureRecognizer:panGesture];
}

代码的主要工作

1) 初始化SliderViewController和CenterViewController两个子控制器,并且将各自的View添加到父控制器的View中。

2) 定义手势方法,用来执行滑动手势。

代码中用了很多宏定义,清单如下:

#define kDefaultCoverAlpha 0.9
#define kDefaultScale 0.6
#define kDefaultTranslation 50
#define kSliderViewWidth 120

kDefaultCoverAlpha 就是SliderViewController的View的遮罩层View的透明度;因为在手势滑动过程中,遮罩层的透明度会一直改变的,以达到阴影的效果。

kDefaultScale 和 kDefaultTranslation 分别是SliderViewController的View的初始状态下的缩放及平移大小。

下面三句代码就可以让SliderViewController的View初始状态时呈现为 "图三" 的状态:

CGAffineTransform scaleTransform = CGAffineTransformMakeScale(kDefaultScale, kDefaultScale);
CGAffineTransform translationTransform = CGAffineTransformMakeTranslation(kDefaultTranslation, 0);
self.slideMenuVC.view.transform = CGAffineTransformConcat(scaleTransform, translationTransform);

kSliderViewWidth 就是SliderViewController的View “满高度” 显示在屏幕上面的时候的宽度。

2. 白色按钮点击事件的处理相关代码:

- (void)toggleSideMenu {
    CGFloat ratio = self.centerNavVC.view.x / kSliderViewWidth;
    BOOL isOpen = ratio == 1.0;
    CGFloat toggleProgress = isOpen ? 0.0 : 1.0;
    [UIView animateWithDuration:1 animations:^{
        [self setToPercent:toggleProgress];
    }];
}

- (void)setToPercent:(CGFloat)progress {
    self.centerNavVC.view.x = progress * kSliderViewWidth;

    CGFloat alpha = kDefaultCoverAlpha * (1 - progress);
    self.cover.alpha = alpha;
    CGFloat scale = kDefaultScale + (1 - kDefaultScale) * progress;
    CGAffineTransform scaleTransform = CGAffineTransformMakeScale(scale, scale);
    CGFloat translation = kDefaultTranslation * (1 - progress);
    CGAffineTransform translationTransform = CGAffineTransformMakeTranslation(translation, 0);
    self.slideMenuVC.view.transform = CGAffineTransformConcat(scaleTransform, translationTransform);
}

这里主要的代码就是平移和缩放的处理,由于和在ViewDidload中初始化的操作基本类似,只是最终状态有所不同,我就不加说明了。

3. 手势的相关代码:

- (void)handleGesture:(UIPanGestureRecognizer *)pan {

    if(pan.state == UIGestureRecognizerStateCancelled || pan.state == UIGestureRecognizerStateEnded){
        CGFloat formerX = self.centerNavVC.view.x;
        if(formerX > kSliderViewWidth * 0.5 && formerX <= kSliderViewWidth){
            formerX = kSliderViewWidth;
        } else{
            formerX = 0;
        }

        CGFloat progress = formerX / kSliderViewWidth;
        [UIView animateWithDuration:1 animations:^{
            [self setToPercent:progress];
        }];

        return;
    }

    if(pan.state == UIGestureRecognizerStateBegan){
        // 一定要将首次按下的起始点记录下来,作为参考点
        self.relativeX = self.centerNavVC.view.x;
    }

    CGPoint translation = [pan translationInView:pan.view];
    CGFloat actualX = self.relativeX + translation.x;
    if (actualX >= kSliderViewWidth) {
        actualX = kSliderViewWidth;
    }

    if (actualX <= 0) {
        actualX = 0;
    }

    CGFloat progress = actualX / kSliderViewWidth;
    [self setToPercent:progress];
}

1. 手势结束或者终止的时候,判断CenterViewController的View的x值,如果小于 0.5 * kSliderViewWidth,动画执行到x = 0 的位置; 反之,动画执行到x = kSliderViewWidth的位置。

2. 手势刚刚开始的时候,一定要记录下CenterViewController的View的x值, 因为后面的移动的操作都是相对于它的,如果想仔细观察,大家可以打印它的值看看。

3. 代码中actualX的值就是手指当前所在屏幕上面的实时的x值,并且限定了手势动画的范围为0到kSliderViewWidth。

样式三

功能分析:

1. 侧边栏有3D的透视效果。

2. 导航栏上面的白色按钮也会有旋转效果。

3. 点击侧边栏上面的Item,有收起侧边栏的动画。

注意:这里的架构模式为"图二"并不是"图一"。很多人也许会疑惑。看动画效果仿佛就是 "图一" 的设计。下面通过代码一一解释。

一、 侧边栏有3D的透视效果分析

根控制器ContainerViewController 代码分析:

1. ViewDidLoad进行初始化工作,代码如下:

- (void)viewDidLoad {
    [super viewDidLoad];

    CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
    CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;

    // Center VC
    CenterViewController *centerVC = [[CenterViewController alloc] init];
    self.centerNavVC = [[UINavigationController alloc] initWithRootViewController:centerVC];
    self.centerNavVC.view.frame = CGRectMake(0, 0, screenWidth, screenHeight);
    [self addChildViewController:self.centerNavVC];
    [self.view addSubview:self.centerNavVC.view];

    // Slide VC
    self.slideMenuVC = [[SlideMenuViewController alloc] init];
    self.slideMenuVC.view.frame = CGRectMake(-kSliderViewWidth, 0, kSliderViewWidth, screenHeight);
    [self addChildViewController:self.slideMenuVC];
    [self.view addSubview:self.slideMenuVC.view];
    self.slideMenuVC.centerNavViewController = self.centerNavVC;

    UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
    [self.view addGestureRecognizer:panGesture];
}

看起来和方式二的初始化代码很像,只是注意到SliderViewController的View的层次关系是在CenterViewController的View的前面的,并且它的View的x初始值是 -kSliderViewWidth。

我这里定义的kSliderViewWidth的宏如下面:

#define kSliderViewWidth 120

2. 手势的代码直接复用方式二的代码,只是手势中调用的setToPercent: 方法有所改变。

- (void)setToPercent:(CGFloat)progress {
    self.slideMenuVC.view.layer.transform = [self menuTransformForPercent:progress];
    self.coverView.alpha = 1 - progress;
    [self.slideMenuVC.view addSubview:self.coverView];
    self.centerNavVC.view.x = progress * kSliderViewWidth;
}

方法中的最后一句代码依旧是CenterViewController的View的拖拽效果。第2,3句代码是为了调节遮罩的程度。SliderViewController的View显示出来的越多,遮罩程度越轻;显示出来的越少,遮罩程度越重;下面的代码就是coverView的懒加载:

- (UIView *)coverView {
    if (!_coverView) {
        _coverView = [[UIView alloc] init];
        _coverView.userInteractionEnabled = NO;
        _coverView.frame = self.slideMenuVC.view.bounds;
        _coverView.backgroundColor = [UIColor blackColor];
    }
    return _coverView;
}

setToPercent: 代码的第一句就是透视动画执行的关键部分。而且这个效果只作用于SliderViewController的View上面,由于是3D动画,所以transform变换是layer的操作。具体调用的menuTransformForPercent: 方法实现如下:

- (CATransform3D)menuTransformForPercent:(CGFloat)percent {
    CATransform3D identity = CATransform3DIdentity;
    identity.m34 = -1.0 / 1000;

    CGFloat remainingPercent = 1.0 - percent;
    CGFloat angle = remainingPercent * (-M_PI_2); 

    CATransform3D rotationTransform = CATransform3DRotate(identity, angle, 0, 1, 0);
    CATransform3D translationTransform = CATransform3DMakeTranslation(percent * kSliderViewWidth, 0, 0);
    return CATransform3DConcat(rotationTransform, translationTransform);
}

代码中使用了单位矩阵的m34属性,关于这个属性不明白的,请参照《CATransform3D 特效详解》。简言之,透视效果就是依据
"近大远小" 这一视觉特性,使人感觉空间是3D的呈现效果。而SliderViewController的View不但有旋转动画,也有平移动画。旋转的角度是90°,平移的大小是kSliderViewWidth。而且这两个动画是同时执行的,所以使用CATransform3DConcat将两个动画"合并"起来。注意代码中,旋转角度的初始值是 -M_PI_2, 即它是垂直面向我们的,也就是看不见 (Just Imagine)。

讲解了这么多,我们现在看看手势操作之后的效果。

但是大家注意到:这里是3D旋转效果,而不是2D平移。3D的旋转必须考虑到旋转轴(就是应该绕着哪里进行旋转)。在执行手势操作的时候,两个View之间会出现间隙,并且间隙距离有时候大,有时候小。导致出现这种现象的原因就是SliderViewController的View所绕的旋转轴不正确。默认情况,View所绕着的旋转轴是正中心的垂直轴,因为他们的锚点是(0.5, 0.5)。 但如果想旋转的时候,想让SliderViewController的View右边一直紧挨着
CenterViewController的View的左边,应该设置旋转轴是SliderViewController的View的右边,即改变它的锚点为(1, 0.5) 即可。转换效果图如下:

所以在ViewDidload代码中,加入下面一句代码:

self.slideMenuVC.view.layer.anchorPoint = CGPointMake(1, 0.5);

我们再看看运行效果:

此时,虽然两者之间有空白的间隙,但是注意到不管怎么移动,两只之间的空隙的距离是保持一致的,也就是说设置锚点值为(1, 0.5) 是起作用的,让其绕着右边进行旋转。而这个空白间隙,我们很容易想象到,是因为没有设置position造成的。所以在ViewDidload代码中加入下面的代码,一切就OK了。

self.slideMenuVC.view.layer.position = CGPointMake(0, screenHeight * 0.5);

3. 消除锯齿现象

注意到,在慢速拖拽的时候,可以看到SliderViewController中的View的各个Item交界处有一些锯齿现象。大图如下:

这个时候,我们可以进行光栅化处理。

Core Animation 在动画过程中,会连续重绘View上面的所有内容并且不停的计算所有移动元素的透视大小等。而光栅化处理,会告诉Core Animation缓存layer的所有内容为一个Image,然后执行的动画效果其实是在处理这个缓存的Image,不需要进行View的重绘工作了。这样处理:1. 提高了动画执行的效率;2. 消除了锯齿现象。

所以我们可以在UIGestureRecognizerStateBegan手势开始的代码中,加入下面两句,防止锯齿现象。

self.slideMenuVC.view.layer.shouldRasterize = YES;
self.slideMenuVC.view.layer.rasterizationScale = [UIScreen mainScreen].scale;

当然,缓存成图片也是一个很耗内存的操作,所以,我们应该在动画结束之后,立刻取消光栅化,释放内存。所以在手势Cancel或者End的时候,加入以下代码:

self.slideMenuVC.view.layer.shouldRasterize = NO;

二、 导航栏上面的白色按钮也会有旋转效果

大家注意到:导航控制器上面的白色按钮的动画,有一种旋转的现象,但又不是普通的绕着x或者y轴的旋转。因为它是绕着x,y轴的斜线方向上面的旋转。

旋转完180°之后,这个横条就会变成竖条(Just Imagine)。

但由于这个按钮是导航控制器的leftBarButtonItem,是导航控制器的一部分;当你进行3D旋转的时候,会改变导航控制器上面本来UI元素的层级关系,所以当按钮旋转到导航栏Title的后面的时候,Title显示,当旋转到导航栏Title的前面的时候,Title会被遮挡;因为在旋转过程中,Title会一直闪烁。解决的办法就是旋转button自己内容的imageView而不是button本身。因为button是相对于导航栏的,而button中的imageView是相对于button的,imageView的旋转不会影响到导航栏本身。所以在setToPercent:
中追加以下代码可以完成旋转工作。

CenterViewController *centerVC = [self.centerNavVC.childViewControllers firstObject];
UIButton *toggleButton = centerVC.flipButton;
// 不能直接旋转Button,因为它是导航条的一部分,会影响title的显示
toggleButton.imageView.layer.transform = CATransform3DMakeRotation(progress * M_PI, 1, 1, 0);

最终旋转的流程图如下:

三、 点击侧边栏上面的Item,有收起侧边栏的动画

这个操作就非常简单了,可以使用block或者代理来完成。我是使用代理来实现的。

1. 在SliderViewController中定义代理quickView代理,并且在点击侧边栏每一项的时候调用代理方法。代码如下:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSArray *items = [MenuItemsHelper sharedMenuItems].items;
    MenuItem *item = items[indexPath.row];
    CenterViewController *centerVC = [self.centerNavViewController.childViewControllers firstObject];
    centerVC.item = item;
    if ([self.delegate respondsToSelector:@selector(quickView:)]) {
        [self.delegate quickView:self];
    }
}

2. ContainerViewController成为代理并且实现代理方法。

self.slideMenuVC.delegate = self;
- (void)quickView:(SlideMenuViewController *)slideMenuVC {
    [self toggleSideMenu];
}
- (void)toggleSideMenu {
    CGFloat ratio = self.centerNavVC.view.x / kSliderViewWidth;
    BOOL isOpen = ratio == 1.0;
    CGFloat toggleProgress = isOpen ? 0.0 : 1.0;
    [UIView animateWithDuration:0.25 animations:^{
        [self setToPercent:toggleProgress];
    } completion:^(BOOL finished) {
        if (isOpen) {
            [self.coverView removeFromSuperview];
        }
    }];
}

至此,整个的侧边栏动画效果就算总结完毕了。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-02 19:07:11

动画特效十一:侧边栏效果的相关文章

用CSS3动画特效实现弹窗效果

提示:如果大家觉得本篇实现的弹窗效果有用,可持续关注.接下会添加更多效果并且封装成插件,这样使用就方便了. CSS3特殊效果 CSS3为用户添加了三个特殊效果的处理方式:过渡.动画.变化.当用户和界面元素交互时,使用这些特殊样式可大大改善用户的体验效果.这些效果直接由浏览器引擎处理,可以节省开销.尽管如此,它们也会耗费大量的处理能力,尤其是一些复杂的WEB页面上.即使是最基本的效果,也是如此.本篇的目的只是熟悉下这三种CSS处理效果,不推荐在实际系统中大篇幅使用. 温馨提示:请谨慎大篇幅使用这些

html5炫酷购物车结算动画特效

这是一款效果十分炫酷的html5购物车结算动画特效插件.该购物车结算动画提供了4种效果,每种效果都使用CSS3来制作炫酷的动画特效,这些效果使用户的购物结算体验大大的增强了. 在线演示:http://www.htmleaf.com/Demo/201501231255.html 下载地址 :http://www.htmleaf.com/html5/html5muban/201501231254.html

html5和CSS3超酷购物车结算动画特效

这是一款效果十分炫酷的html5和css3购物车结算动画特效插件.该购物车结算动画提供了4种效果,每种效果都使用CSS3来制作炫酷的动画特效,这些效果使用户的购物结算体验大大的增强了. 如何设计购物车结算功能界面才能让用户得到最好的体验,如果设计得好,它可以为用户带来一个愉快的购物历程,如果设计得不好,那么一大堆东西堆积在购物车中,用户也不知道如何去处理它们.这个插件正是为解决这个问题而设计的. 在线演示:http://www.htmleaf.com/Demo/201501231255.html

Web API (scroll系列)、(仿淘宝侧边栏效果实现)、(mouseenter与mouseover的区别)、(动画的原理)、(缓动动画)

一 .三大系列中的scroll系列 : (1)scrollLeft |  scrollTop  :水平   |   垂直方向滚动出去的距离  : (2)scrollWidth |  scrollHeight   :内容的真是宽度  |  高度   : (3)滚动整个页面的时候  :   window . pageYOffset   : 二 .仿淘宝侧边栏效果实现 : 1.  找到关心的元素对象  : (1)banner区域  元素对象  : (2)侧边栏的元素对象   : (3)主体部分元素对象

HTML5 SVG超酷运动模糊动画特效

这是一款效果十分炫酷的HTML5 SVG制作的运动模糊动画特效.该SVG运动模糊特效有三种效果:画廊效果.模态窗口效果和侧边栏菜单效果.它们使用SVG过滤器和js及css相配合,在元素运动时产生动态模糊的炫酷效果. 运动模糊是一种技术,广泛应用于一般的运动图形和动画,使运动更加流畅自然.维基百科对运动模糊的解释是:运动模糊是明显的在静止图像或图像序列如电影或动画的快速移动的物体.它是当图像被记录的变化对单帧记录过程中,由于快速运动或长时间曝光的结果. 效果演示:http://www.htmlea

8款HTML5动画特效推荐源码

1.HTML5 Canvas发光Loading动画 之前我们分享过很多基于CSS3的Loading动画效果,相信大家都很喜欢.今天我们要来分享一款基于HTML5 Canvas的发光Loading加载动画特效.Loading旋转图标是在canvas画布上绘制的,整个loading动画是发3D的视觉效果,HTML5非常强大. 在线演示 源码下载 2.jQuery球状放大镜特效插 今天我们要来分享一款基于jQuery的放大镜特效插件,和其他放大镜不同的是,这款jQuery放大镜插件是球状的,看上去有3

CSS3单选框动画特效实现步骤详解

在前端开发中,我们常常使用CSS3技术来实现单选框的动画特效,对于前端菜鸟而言,可能对这部分内容还不是很熟悉,今天就和大家分享一个这方面的案例,希望对大家学习CSS3技术有所帮助,一起来看看吧. 首先,来看一下我们的第一个特效 注意,这个地方的黄点不是我们特效的一部分,这个黄点之所以存在是我使用的屏幕录制软件自带的.可以很清楚的看到这个特效就是当我们点击的时候,黑点会以一种缩放的动画显示出来,下面来看看具体如何实现. <divclass="radio-1"> <inp

html5跟随鼠标炫酷网站引导页动画特效

html5跟随鼠标炫酷网站引导页动画特效一款非常不错的引导页,文字效果渐变,鼠标跟随出绚丽的条纹.html5炫酷网站引导页,鼠标跟随出特效. 体验效果:http://hovertree.com/texiao/html5/ 效果图: 以下是源代码: 1 <!DOCTYPE html> 2 <html xmlns="http://www.w3.org/1999/xhtml"> 3 <head> 4 <meta http-equiv="Co

有时候就是看不进论文-jQuery动画特效篇&amp;MySQL

hi 早上知道新的乱斗模式后,没忍住开了几把,然后就无心论文了...用这个来破吧 1.jQuery -----动画特效----- ----调用show()和hide()方法显示和隐藏元素 show()和hide()方法用于显示或隐藏页面中的元素,它的调用格式分别为: $(selector).hide(speed,[callback])和$(selector).show(speed,[callback]) 参数speed设置隐藏或显示时的速度值,可为“slow”.“fast”或毫秒数值,可选项参数