[译]《Motion Design for iOS》(四十二)

构建立即响应的按钮

你玩过Loren Brichter的游戏Letterpress吗?我很喜欢的Loren构建的一个关于界面的东西可能不是每个人都明显喜欢的:我喜欢每个按钮在用户按下时立即切换到一个不同的状态的样子。绝对不会延迟。这不是一个简单实现的行为,因为即使你可以将一个图片设为UIButtonUIControlStateHighlighted状态图,它也只会在点击发生后一小会启动,而且它不允许更进一步的代码来运行它。如果我想要在用户点击一个UIButton后立即运行一个动画,我就不得不自己写一个简单的自定义按钮类。但首先,先来看一看我们要构建的是什么。


image

如果我想要在用户点击后立即运行代码,我就不得不自己写一个好的UIButton子类,这样我就可以重写一些方法,即 -touchesBegan:withEvent: 和 -touchesEnded:withEvent:。iOS中的每个界面的控制都从UIResponder继承了这些方法,它是一个处理所有触摸控制事件的父类。有了子类,我就可以塞一些自己的代码来在这些方法启动的时候运行。来看看DTCTestButton的实现文件,这是我们的按钮子类,会为我们处理一些魔法。

#import "DTCTestButton.h"
#import "POP.h"

@implementation DTCTestButton

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // 自定义一些按钮第一次被点击时要运行的代码

    [super touchesBegan:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    // 自定义一些按钮不再被点击时要运行的代码

    [super touchesEnded:touches withEvent:event];
}

@end

我们这里只定义了两个方法,我们想要将我们的代码放到这些方法里面去。当子类化一个苹果提供的对象,比如UIButton时,做一个好的城市居民并确保调用super的关于这些方法的实现是很重要的,因为我们不知道苹果在这两个方法中需要运行什么代码,而且不想破坏按钮的默认行为。我们调用super后,就可以在这两个方法中添加任何我们想要的行为。

让我们添加一个Pop动画到 -touchesBegan:withEvent:中去。

POPSpringAnimation *scale = [self pop_animationForKey:@"scale"];

if (scale) {
    scale.toValue = [NSValue valueWithCGPoint:CGPointMake(0.8, 0.8)];
} else {
    scale = [POPSpringAnimation animationWithPropertyNamed:kPOPViewScaleXY];
    scale.toValue = [NSValue valueWithCGPoint:CGPointMake(0.8, 0.8)];
    scale.springBounciness = 20;
    scale.springSpeed = 18.0f;
    [self pop_addAnimation:scale forKey:@"scale"];
}

这和我们之前写的Pop代码有点不同。当使用Pop来构建好的响应动画去关联触摸动作时,一个聪明的做法是看看是否已经有一个Pop动画关联到这个视图或者layer了。如果有,只要更新已经存在的动画的toValue属性就可以了。Pop知道当前的值是什么并且已经设置好弹性和速度变量了,所以你不用做任何其他的事情。这避免了添加另一个错误的Pop动画来操作同样的值(在这个例子中,是kPOPViewScaleXY),这会造成愚蠢的结果。通过使用现存的动画,Pop可以优雅地从它的当前位置修改到你设置的新的toValue并进行一个漂亮、平滑的过度。这也是为什么Pop动画有一个名字:这样你就可以通过给出你之前设置的动画的名字来询问视图或者layer它们是否有已经添加进去的Pop动画并获取到动画对象。

如果动画不是已经存在,我们就和平常一样创建一个新的Pop动画对象,设置弹簧的动作属性,比如弹性,设置toValue,然后添加动画到视图或者layer上。在这个例子中,我们动画了视图的尺寸,所以我们将动画添加到视图上。

现在让我们在触摸事件结束时做同样的事情。这次代码放在 -touchesEnded:withEvent:中。

POPSpringAnimation *scale = [self pop_animationForKey:@"scale"];

if (scale) {
    scale.toValue = [NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)];
} else {
    scale = [POPSpringAnimation animationWithPropertyNamed:kPOPViewScaleXY];
    scale.toValue = [NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)];
    scale.springBounciness = 20;
    scale.springSpeed = 18.0f;
    [self pop_addAnimation:scale forKey:@"scale"];
}

如果你看看触摸事件开始时0.8的toValue以及触摸结束时的1.0的toValue,你就可以猜到整个动画会在用户点击按钮时稍微收缩按钮的尺寸,然后会在他们停止触摸时弹回完整的尺寸。完全正确!这里是它现在的样子。


image

很有意思!让我们再加一点点旋转动画来增色。它基本上和我们已经添加的代码一样,只是重复它,修改动画类型,然后改变toValue值。这里是完整的代码,以及一些注释。

// 当用户开始点击时立即调用
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {    
    // 看动画是否已经被添加到视图或者layer上
    POPSpringAnimation *scale = [self pop_animationForKey:@"scale"];
    POPSpringAnimation *rotate = [self.layer pop_animationForKey:@"rotate"];

    // 如果scale动画已经存在,就设置toValue
    if (scale) {
        scale.toValue = [NSValue valueWithCGPoint:CGPointMake(0.8, 0.8)];
    } else {
        // 如果不存在,就创建并添加它
        scale = [POPSpringAnimation animationWithPropertyNamed:kPOPViewScaleXY];
        scale.toValue = [NSValue valueWithCGPoint:CGPointMake(0.8, 0.8)];
        scale.springBounciness = 20;
        scale.springSpeed = 18.0f;
        [self pop_addAnimation:scale forKey:@"scale"];
    }

    // 如果旋转动画已经存在,就设置toValue
    if (rotate) {
        rotate.toValue = @(M_PI/6); // 旋转到1/6th π角度
    } else {
        // 旋转动画时layer上的,所以我们添加到layer上去
        rotate = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotation];
        rotate.toValue = @(M_PI/6);
        rotate.springBounciness = 20;
        rotate.springSpeed = 18.0f;

        // 添加到layer上,而不是view
        [self.layer pop_addAnimation:rotate forKey:@"rotate"];
    }

    [super touchesBegan:touches withEvent:event];
}

// 在用户离开手指时立即调用
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    // 看动画是否存在(由于这是用户离开时,基本是已经存在的)
    POPSpringAnimation *scale = [self pop_animationForKey:@"scale"];
    POPSpringAnimation *rotate = [self pop_animationForKey:@"rotate"];

    if (scale) {
        // 拉伸回1.0的完整尺寸
        scale.toValue = [NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)];
    } else {
        scale = [POPSpringAnimation animationWithPropertyNamed:kPOPViewScaleXY];
        scale.toValue = [NSValue valueWithCGPoint:CGPointMake(1.0, 1.0)];
        scale.springBounciness = 20;
        scale.springSpeed = 18.0f;
        [self pop_addAnimation:scale forKey:@"scale"];
    }

    if (rotate) {
        // 旋转回0角度的初始位置
        rotate.toValue = @(0);
    } else {
        rotate = [POPSpringAnimation animationWithPropertyNamed:kPOPLayerRotation];
        rotate.toValue = @(0);
        rotate.springBounciness = 20;
        rotate.springSpeed = 18.0f;

        // 再次确保添加你的layer动画到layer上去。我曾经失误过很多次,这会导致一个有趣的bug :)
        [self.layer pop_addAnimation:rotate forKey:@"rotate"];
    }

    [super touchesEnded:touches withEvent:event];
}

动画代码是重复的。简单,但是重复。它的一个缺点是需要很多行代码来完整构建你的动画,但优点是能让你练习写很多动画代码,所以我认为你可以学的更快。

再一次,这里是我们构建的最终动画。它是一个很有趣的效果,会在用户点击按钮时立即启动,它会让你的界面感觉响应很快。这里的弹性效果很显著,所以当添加动画到你的真实app界面时,去使用一会app的动画,并确保它们的速度和动作时合适且不分散注意力的。


image

现在让我们来用Pop做一些有趣的东西!

查看完整合集(喜欢请打星~):https://github.com/Cloudox/Motion-Design-for-iOS


查看作者首页

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容