zoukankan      html  css  js  c++  java
  • 卡片式弹出窗口

    Apple公司在WWDC 2016前后带来了10个重大更新,其中重中之重可谓是iOS 10的更新了。iOS 10被称作iOS 7之后迎来的最大升级,在所有的革新中,无论在通知中心的UI修改,还是各处的3D Touch弹出层,都能看到卡片式设计的影子。笔者不是设计师,但是以一个iOS开发者的角度来看,iOS的扁平化设计风格,逐渐正在往在Google I/O 2014提出的卡片式设计靠拢。(关于更多的卡片式设计资料可查看这里)。

    处此大势之下,我也跟风来搞一波。首先先感谢原型图的作者Sarah,虽然我不是Dribbble的Pro用户,无法在您的作品上留言,但之后我会及时的联系你。以下就是原型图的效果了:

    动画分析

    观察全部特效,我们发现了以下步骤

    • 弹出卡片弹出窗口

    • 自定义UITextField填写进度动画

    • 卡片缩小至消失

    • 弹出logo动画片尾

    其中我们忽略掉一个效果,就是在原型图中,触摸键移动到NEXT按钮上的动效。因为在手机上,我们不可能持续在屏幕上滑动手指,而是采用轻按的方式。想必设计师当时忘记了使用场景,当做web来设计的。

    一番动画分析之后,我们逐步击破。

    动画拆解

    对于弹出层的构建

    笔者在实现之前,对于这个弹出的感觉就是在View上面附加一层View进行实现。我在awesome-ios搜索了其他其他关于Popup View实现的方式,在主流的NMPopUpView、CNPPopup等都是使用了ViewController来实现,虽然现在还没有明确这样做的好处,我觉的可能是减少了当前视图的负担。因为在弹出层中,有许多需要与触控手势的控件,所以对于View的负担过重。我们规划一下文件结构:

    ├── DGPopUpViewController

       ├── DGPopUpView.h

       ├── DGPopUpView.m

       ├── DGPopUpViewController.h

       ├── DGPopUpViewController.m

       ├── DGPopUpViewLoginButton.h

       ├── DGPopUpViewLoginButton.m

       ├── DGPopUpViewTextView.h

       └── DGPopUpViewTextView.m

    结构一目了然,ViewController持有PopUpView,PopUpView中带有其他的自定义控件。我们写一个对外接口,来将这层ViewController的视图显示在当前视图上,但是其控制器还要保持原本的控制器。另外要注意的是,在添加背景色的时候,我们用到了UIColor的colorWithAlphaComponent方法,具体的作用是对当前颜色具有透明状态。当然我们使用更改alpha也可修改透明状态,但是对View的修改,会影响到该View的子视图。

    #pragma mark - Override

    #define DGPopUpViewBackgroundColor [[UIColor colorWithRed: 245 / 255.f green: 245 / 255.f blue: 245 / 255.f alpha: 1] colorWithAlphaComponent: 0.8]

    - (instancetype) init {

        isOpen = YES;

        self.view.backgroundColor = DGPopUpViewBackgroundColor;

        [self.view addSubview: self.popUpCloseButton];

        [self.view addSubview: self.popUpView];

        // 尾部动画通知实现

        [[NSNotificationCenter defaultCenter] addObserver: self

                                                 selector: @selector(endAnimation)

                                                     name: @"end_animation"

                                                   object: nil];

        // 尾部动画调试

        // [self endAnimation];

        return self;

    }

    #pragma mark - Add SubView

    - (void) showInView: (UIView *)aView animated: (BOOL)animated {

        self.view.center = aView.center;

        [aView addSubview: self.view];

        if (animated) {

            [self showAnimation];

        }

    }

    写完入口方法之后,我们需要做一些处理。例如,在弹出层弹出后,若点击非PopUpView部分,即可将PopUpView退出的方法即相关动画。另外,还需要构建弹出动画、尾部的logo动画。

    #pragma mark - Removew SubView

    - (void) removeAnimation {

        self.popUpView.transform = CGAffineTransformMakeScale(1, 1);

        [UIView animateWithDuration: 0.6

                              delay: 0

                            options: UIViewAnimationOptionCurveEaseInOut

                         animations: ^{

                             self.popUpView.alpha = 0;

                         }

                         completion: ^(BOOL finished) {

                             [self.view removeFromSuperview];

                         }];

    }

    - (void) endAnimation {

        self.endView = [[UIImageView alloc] initWithImage: [UIImage imageNamed: @"end_logo"]];

        self.endView.center = self.view.center;

        [self.view addSubview: self.endView];

        self.endView.transform = CGAffineTransformMakeScale(0, 0);

        [UIView animateWithDuration: 1.4

                              delay: 0

                            options: UIViewAnimationOptionCurveEaseInOut

                         animations: ^{

                             self.endView.transform = CGAffineTransformMakeScale(0.6, 0.6);

                         }

                         completion: ^(BOOL finished) {

                         }];

    }

    #pragma mark - Animation

    - (void) showAnimation {

        self.view.alpha = 0.0;

        self.popUpView.transform = CGAffineTransformMakeScale(0, 0);

        [UIView animateWithDuration: 0.25

                         animations: ^{

                             self.view.alpha = 1.0;

                         }

                         completion: ^(BOOL finished) {

                             [self showPopUpView];

                         }];

    }

    - (void) showPopUpView {

        self.popUpView.alpha = 0.5;

        [UIView animateWithDuration: 0.8

                              delay: 0

                            options: UIViewAnimationOptionCurveEaseOut

                         animations: ^{

                             self.popUpView.transform = CGAffineTransformMakeScale(1, 1);

                             self.popUpView.alpha = 1;

                         }

                         completion: ^(BOOL finished) {

                         }];

    }

    弹出层

    在弹出层View中,我们需要做的就是在上面增加控件。弹出层需要持有的控件有三个:标题Label、自定义TextField、按钮Button。另外,需要他自身需要处理点击按钮正常退出的动画。这种弹出层退出方式与在控制器中的退出方式很显然响应者不同。在用户主动请求退出的过程中,用户主要是以控制器View作为交互对象,而点击Next按钮后的退出时与弹出层View为交互对象。这样写能让我们的代码更清晰,可读性加强。

    #pragma mark - Close Animation

    - (void) closeAnimation {

        self.transform = CGAffineTransformMakeScale(1, 1);

        [UIView animateWithDuration: 0.6

                              delay: 0.3

                            options: UIViewAnimationOptionCurveEaseInOut

                         animations: ^{

                             self.transform = CGAffineTransformMakeScale(0.01, 0.01);

                             self.superview.alpha = 1;

                         }

                         completion: ^(BOOL finished) {

                             [[NSNotificationCenter defaultCenter] postNotificationName: @"end_animation" object: nil];

                             [self removeFromSuperview];

                         }];

    }

    自定义控件

    这里用到的自定义有两个,第一个是NEXT的Button。仔细观察一下原型图,Button主要有两个特点:

    • 渐出动画是在PopUpView之前就开始,而且是独立渐出

    • Button的背景色是一种渐变色

    对于第一个动效,只需要在PopUpView启动退出动画之前,先执行Button内部的退出动画。动画无论退出还是出现都是类似的CGAffineTransformMakeScale效果,无需讲述。

    #pragma mark - Animation

    - (void) pressAnimation {

        NSLog(@"click");

        self.transform = CGAffineTransformMakeScale(1, 1);

        [UIView animateWithDuration: 0.6

                              delay: 0

                            options: UIViewAnimationOptionCurveEaseInOut

                         animations: ^{

                             self.transform = CGAffineTransformMakeScale(0.01, 0.01);

                         }

                         completion: ^(BOOL finished) {

                             [self removeFromSuperview];

                         }];

        [[NSNotificationCenter defaultCenter] postNotificationName: @"NEXT_Button" object: nil];

    }

    渐变色的处理我们可以使用CAGradientLayer这个类。CAGradientLayer是用来生成两种或更多颜色平滑渐变,使用改类的好处在于绘制时使用了硬件加速(官方文档)。我们只要依次写出各个关键点,就可以确定颜色的一个渐变方向。直接看代码,很容易就能看懂。

    #pragma mark - Set Color

    - (void) setColor {

        CAGradientLayer *gradientLayer = [CAGradientLayer layer];

        gradientLayer.frame = self.bounds;

        gradientLayer.locations = @[@0.3, @0.8];

        gradientLayer.colors = @[(__bridge id)[UIColor colorWithRed: 56 / 255.f green: 195 / 255.f blue: 227 / 255.f alpha: 1].CGColor,

                                 (__bridge id)[UIColor colorWithRed: 16 / 255.f green: 156 / 255.f blue: 197 / 255.f alpha: 1].CGColor];

        gradientLayer.startPoint = CGPointMake(0, 0);

        gradientLayer.endPoint = CGPointMake(1, 0);

        [self.layer addSublayer: gradientLayer];

    }

    我们最后来说说自定义的TextField,TextField我们仅仅是在下方增加了一个类似于进度条的动画。具体的逻辑是这样,当我们手势响应一个TextField之后,我们就会启动动画,让进度条滑动到定点位置。而当我们退出响应该TextField的时候,先要检查text部分是否为空,然后在决定进度条是否要保存状态。先给出代码,解释一下。

    #pragma mark - UITextFieldDelegate

    - (void)textFieldDidBeginEditing: (UITextField *)textField {

        // 确定动画类型

        CABasicAnimation *basic = [CABasicAnimation animationWithKeyPath: @"transform.scale.x"];

        // 确定锚点

        [self.progressLine.layer setAnchorPoint: CGPointMake(0, 0.5)];

        // 持续时间

        basic.duration = 0.3;

        // 重复次数

        basic.repeatCount = 1;

        // 结束后是否删除

        basic.removedOnCompletion = NO;

        // 状态点数值

        basic.fromValue = [NSNumber numberWithFloat: 1];

        basic.toValue = [NSNumber numberWithFloat: 280];

        // 完成时保存状态

        basic.fillMode = kCAFillModeForwards;

        // 增加缓动函数

        basic.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn];

        [self.progressLine.layer addAnimation: basic forKey: nil];

    }

    - (void)textFieldDidEndEditing:(UITextField *)textField{

        if ([self.textField.text isEqualToString: @""]) {

            CABasicAnimation *basic = [CABasicAnimation animationWithKeyPath: @"transform.scale.x"];

            [self.progressLine.layer setAnchorPoint: CGPointMake(0, 0.5)];

            basic.duration = 0.3;

            basic.repeatCount = 1;

            basic.removedOnCompletion = NO;

            basic.fromValue = [NSNumber numberWithFloat: 280];

            basic.toValue = [NSNumber numberWithFloat: 1];

            basic.fillMode = kCAFillModeForwards;

            basic.timingFunction = [CAMediaTimingFunction functionWithName: kCAMediaTimingFunctionEaseIn];

            [self.progressLine.layer addAnimation: basic forKey: nil];

        }

    }

    这里我们不在对View层进行动画,而是改用CABasicAnimation。好处仍旧是硬件加速。这里我们特别要注意一个地方。刚开始接触CABasicAnimation的时候,可能不太理解fromValue和toValue。这两个数值既不反应屏幕坐标,也不表示像素数。它的意思是起始尺寸数值的整数倍数。也就是说,这里我们假设进度条的长度为x,则该动画的过程就是把进度条长度线性的从x增长到280x,这里也就反应了一个问题,我们在设定进度条初值的时候不能设置成0。

    结束语

    大体上,整个卡片登录界面的实现思路就是这样,最后再次感谢原型图作者。在进行动画制作之后,强烈建议读者去学习《iOS Core Animation: Advanced Techniques》这本书,之后会对Core Animation会有更深层的认识。


    Github【Desgard_Duan】

    https://github.com/dgytdhy/DGPopUpViewController

  • 相关阅读:
    如何选择大数据应用程序
    Python字符和字符值(ASCII或Unicode码值)转换方法
    Python字符和字符值(ASCII或Unicode码值)转换方法
    论炒币者的自我修养
    论炒币者的自我修养
    区块链是什么,如何评价区块链
    C#封装C++DLL(特别是char*对应的string)
    C#文件夹和文件操作
    VS工程目标文件名设置
    double最大最小值宏定义
  • 原文地址:https://www.cnblogs.com/fengmin/p/5625728.html
Copyright © 2011-2022 走看看