WWDC 2013 Session笔记 - iOS7中弹簧式列表的制作

这是我的WWDC2013系列笔记中的一篇,完整的笔记列表请参看这篇总览。本文仅作为个人记录使用,也欢迎在许可协议范围内转载或使用,但是还烦请保留原文链接,谢谢您的理解合作。如果您觉得本站对您能有帮助,您可以使用RSS或邮件方式订阅本站,这样您将能在第一时间获取本站信息。

本文涉及到的WWDC2013 Session有

  • Session 206 Getting Started with UIKit Dynamics
  • Session 217 Exploring Scroll Views in iOS7

UIScrollView可以说是UIKit中最重要的类之一了,包括UITableView和UICollectionView等重要的数据容器类都是UIScrollView的子类。在历年的WWDC上,UIScrollView和相关的API都有专门的主题进行介绍,也可以看出这个类的使用和变化之快。今年也不例外,因为iOS7完全重新定义了UI,这使得UIScrollView里原来不太会使用的一些用法和实现的效果在新的系统中得到了很好的表现。另外,由于引入了UIKit Dynamics,我们还可以结合ScrollView做出一些以前不太可能或者需要花费很大力气来实现的效果,包括带有重力的swipe或者是类似新的信息app中的带有弹簧效果聊天泡泡等。如果您还不太了解iOS7中信息app的效果,这里有一张gif图可以帮您大概了解一下:

iOS7中信息app的弹簧效果

这次笔记的内容主要就是实现一个这样的效果。为了避免重复造轮子,我对这个效果进行了一些简单的封装,并连同这篇笔记的demo一起扔在了Github上,有需要的童鞋可以到这里自取。

iOS7的SDK中Apple最大的野心其实是想用SpriteKit来结束iOS平台游戏开发(至少是2D游戏开发)的乱战,统一游戏开发的方式并建立良性社区。而UIKit Dynamics,个人猜测Apple在花费力气为SpriteKit开发了物理引擎的同时,发现在UIKit中也可以使用,并能得到不错的效果,于是顺便革新了一下设计理念,在UI设计中引入了不少物理的概念。在iOS系统中,最为典型的应用是锁屏界面打开相机时中途放弃后的重力下坠+反弹的效果,另一个就是信息应用中的加入弹性的消息列表了。弹性列表在我自己上手试过以后觉得表现形式确实很生动,可以消除原来列表那种冷冰冰的感觉,是有可能在今后的设计中被大量使用的,因此决定学上一学。

首先我们需要知道要如何实现这样一种效果,我们会用到哪些东西。毋庸置疑,如果不使用UIKit Dynamics的话,自己从头开始来完成会是一件非常费力的事情,你可能需要实现一套位置计算和物理模拟来使效果看起来真实滑润。而UIKit Dynamics中已经给我们提供了现成的弹簧效果,可以用UIAttachmentBehavior进行实现。另外,在说到弹性效果的时候,我们其实是在描述一个列表中的各个cell之间的关系,对于传统的UITableView来说,描述UITableViewCell之间的关系是比较复杂的(因为Apple已经把绝大多数工作做了,包括计算cell位置和位移等。使用越简单,定制就会越麻烦在绝大多数情况下都是真理)。而UICollectionView则通过layout来完成cell之间位置关系的描述,给了开发者较大的空间来实现布局。另外,UIKit Dynamics为UICollectionView做了很多方便的Catagory,可以很容易地“指导”UICollectionView利用加入物理特性计算后的结果,在实现弹性效果的时候,UICollectionView是我们不二的选择。

如果您在阅读这篇笔记的时候遇到困难的话,建议您可以看看我之前的一些笔记,包括今年的UIKit Dynamics的介绍和去年的UICollectionView介绍。

话不多说,我们开工。首先准备一个UICollectionViewFlowLayout的子类(在这里叫做VVSpringCollectionViewFlowLayout),然后在ViewController中用这个layout实现一个简单的collectionView:

//ViewController.m@interface ViewController ()<UICollectionViewDataSource, UICollectionViewDelegate>
@property (nonatomic, strong) VVSpringCollectionViewFlowLayout *layout;
@endstatic NSString *reuseId = @"collectionViewCellReuseId";@implementation ViewController
- (void)viewDidLoad
{[super viewDidLoad];// Do any additional setup after loading the view, typically from a nib.self.layout = [[VVSpringCollectionViewFlowLayout alloc] init];self.layout.itemSize = CGSizeMake(self.view.frame.size.width, 44);UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:self.layout];collectionView.backgroundColor = [UIColor clearColor];[collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseId];collectionView.dataSource = self;[self.view insertSubview:collectionView atIndex:0];
}#pragma mark - UICollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{return 50;
}- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseId forIndexPath:indexPath];//Just give a random color to the cell. See https://gist.github.com/kylefox/1689973cell.contentView.backgroundColor = [UIColor randomColor];return cell;
}
@end

这部分没什么可以多说的,现在我们有一个标准的FlowLayout的UICollectionView了。通过使用UICollectionViewFlowLayout的子类来作为开始的layout,我们可以节省下所有的初始cell位置计算的代码,在上面代码的情况下,这个collectionView的表现和一个普通的tableView并没有太大不同。接下来我们着重来看看要如何实现弹性的layout。对于弹性效果,我们需要的是连接一个item和一个锚点间弹性连接的UIAttachmentBehavior,并能在滚动时设置新的锚点位置。我们在scroll的时候,只要使用UIKit Dynamics的计算结果,替代掉原来的位置更新计算(其实就是简单的scrollView的contentOffset的改变),就可以模拟出弹性的效果了。

首先在-prepareLayout中为cell添加UIAttachmentBehavior

//VVSpringCollectionViewFlowLayout.m
@interface VVSpringCollectionViewFlowLayout()
@property (nonatomic, strong) UIDynamicAnimator *animator;
@end@implementation VVSpringCollectionViewFlowLayout
//...-(void)prepareLayout {[super prepareLayout];if (!_animator) {_animator = [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self];CGSize contentSize = [self collectionViewContentSize];NSArray *items = [super layoutAttributesForElementsInRect:CGRectMake(0, 0, contentSize.width, contentSize.height)];for (UICollectionViewLayoutAttributes *item in items) {UIAttachmentBehavior *spring = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:item.center];spring.length = 0;spring.damping = 0.5;spring.frequency = 0.8;[_animator addBehavior:spring];}}
}
@end

prepareLayout将在CollectionView进行排版的时候被调用。首先当然是call一下super的prepareLayout,你肯定不会想要全都要自己进行设置的。接下来,如果是第一次调用这个方法的话,先初始化一个UIDynamicAnimator实例,来负责之后的动画效果。iOS7 SDK中,UIDynamicAnimator类专门有一个针对UICollectionView的Category,以使UICollectionView能够轻易地利用UIKit Dynamics的结果。在UIDynamicAnimator.h中能够找到这个Category:

@interface UIDynamicAnimator (UICollectionViewAdditions)// When you initialize a dynamic animator with this method, you should only associate collection view layout attributes with your behaviors.
// The animator will employ thecollection view layout’s content size coordinate system.
- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout*)layout;// The three convenience methods returning layout attributes (if associated to behaviors in the animator) if the animator was configured with collection view layout
- (UICollectionViewLayoutAttributes*)layoutAttributesForCellAtIndexPath:(NSIndexPath*)indexPath;
- (UICollectionViewLayoutAttributes*)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
- (UICollectionViewLayoutAttributes*)layoutAttributesForDecorationViewOfKind:(NSString*)decorationViewKind atIndexPath:(NSIndexPath *)indexPath;@end

于是通过-initWithCollectionViewLayout:进行初始化后,这个UIDynamicAnimator实例便和我们的layout进行了绑定,之后这个layout对应的attributes都应该由绑定的UIDynamicAnimator的实例给出。就像下面这样:

//VVSpringCollectionViewFlowLayout.m
@implementation VVSpringCollectionViewFlowLayout//...-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {return [_animator itemsInRect:rect];
}-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {return [_animator layoutAttributesForCellAtIndexPath:indexPath];
}
@end

让我们回到-prepareLayout方法中,在创建了UIDynamicAnimator实例后,我们对于这个layout中的每个attributes对应的点,都创建并添加一个添加一个UIAttachmentBehavior(在iOS7 SDK中,UICollectionViewLayoutAttributes已经实现了UIDynamicItem接口,可以直接参与UIKit Dynamic的计算中去)。创建时我们希望collectionView的每个cell就保持在原位,因此我们设定了锚点为当前attribute本身的center。

接下来我们考虑滑动时的弹性效果的实现。在系统的信息app中,我们可以看到弹性效果有两个特点:

  • 随着滑动的速度增大,初始的拉伸和压缩的幅度将变大
  • 随着cell距离屏幕触摸位置越远,拉伸和压缩的幅度

对于考虑到这两方面的特点,我们所期望的滑动时的各cell锚点的变化应该是类似这样的:

向上拖动时的锚点变化示意

现在我们来实现这个锚点的变化。既然都是滑动,我们是不是可以考虑在UIScrollView的–scrollViewDidScroll:委托方法中来设定新的Behavior锚点值呢?理论上来说当然是可以的,但是如果这样的话我们大概就不得不面临着将刚才的layout实例设置为collectionView的delegate这样一个事实。但是我们都知道layout应该做的事情是给collectionView提供必要的布局信息,而不应该负责去处理它的委托事件。处理collectionView的回调更恰当地应该由处于collectionView的controller层级的类来完成,而不应该由一个给collectionView提供数据和信息的类来响应。在UICollectionViewLayout中,我们有一个叫做-shouldInvalidateLayoutForBoundsChange:的方法,每次layout的bounds发生变化的时候,collectionView都会询问这个方法是否需要为这个新的边界和更新layout。一般情况下只要layout没有根据边界不同而发生变化的话,这个方法直接不做处理地返回NO,表示保持现在的layout即可,而每次bounds改变时这个方法都会被调用的特点正好可以满足我们更新锚点的需求,因此我们可以在这里面完成锚点的更新。

//VVSpringCollectionViewFlowLayout.m
@implementation VVSpringCollectionViewFlowLayout//...-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {UIScrollView *scrollView = self.collectionView;CGFloat scrollDelta = newBounds.origin.y - scrollView.bounds.origin.y;//Get the touch pointCGPoint touchLocation = [scrollView.panGestureRecognizer locationInView:scrollView];for (UIAttachmentBehavior *spring in _animator.behaviors) {CGPoint anchorPoint = spring.anchorPoint;CGFloat distanceFromTouch = fabsf(touchLocation.y - anchorPoint.y);CGFloat scrollResistance = distanceFromTouch / 500;UICollectionViewLayoutAttributes *item = [spring.items firstObject];CGPoint center = item.center;//In case the added value bigger than the scrollDelta, which leads an unreasonable effectcenter.y += (scrollDelta > 0) ? MIN(scrollDelta, scrollDelta * scrollResistance): MAX(scrollDelta, scrollDelta * scrollResistance);item.center = center;[_animator updateItemUsingCurrentState:item];}return NO;
}@end

首先我们计算了这次scroll的距离scrollDelta,为了得到每个item与触摸点的之间的距离,我们当然还需要知道触摸点的坐标touchLocation。接下来,可以根据距离对每个锚点进行设置了:简单地计算了原来锚点与触摸点之间的距离distanceFromTouch,并由此计算一个系数。接下来,对于当前的item,我们获取其当前锚点位置,然后将其根据scrollDelta的数值和刚才计算的系数,重新设定锚点的位置。最后我们需要告诉UIDynamicAnimator我们已经完成了对冒点的更新,现在可以开始更新物理计算,并随时准备collectionView来取LayoutAttributes的数据了。

也许你还没有缓过神来?但是我们确实已经做完了,让我们来看看实际的效果吧:

带有弹性效果的collecitonView

当然,通过调节dampingfrequencyscrollResistance的系数等参数,可以得到弹性不同的效果,比如更多的震荡或者更大的幅度等等。

这个layout实现起来非常简单,我顺便封装了一下放到了Github上,大家有需要的话可以点击这里下载并直接使用。



转载自:https://onevcat.com/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/458873.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

H5学习之旅-H5列表(8)

列表的基本语法 ol&#xff1a;有序列表 ul&#xff1a;无序列表 li&#xff1a;列表项 dl&#xff1a;列表 dt&#xff1a;列表项 dd&#xff1a;列表描述 常用列表 1.无序列表&#xff1a;使用标签 ul&#xff0c;li 属性&#xff1a;disc&#xff08;默认实心圆&#xff09;…

c语言中用文件处理数据,C语言文件处理 -C语言从文件中读写格式化数据

从图 1 所示的文件 fin.txt 中读取学生姓名、身高和体重&#xff0c;计算并显示它们的平均值&#xff0c;并且将显示结果保存到文件 fout.txt 中。图 1&#xff1a;输入输出文件示例C语言代码清单 1&#xff1a;读取学生姓名、身高和体重&#xff0c;计算并显示它们的平均值#in…

三星+android+7.0+自动纠正单词,升级党必看!三星S/Note系列更新Android 7.0指南

上周&#xff0c;雷科技(微信ID&#xff1a;leitech)曾为大家提供了一份Android 7.0升级预测名单。现在为了增加针对性&#xff0c;这次笔者就以三星最热门的两大旗舰S和Note系列为例&#xff0c;给大家提供一个全方面的更新指南。截至目前&#xff0c;三星S系列和Note系列的正…

互联网年鉴

最近互联网行业&#xff0c;有一个词说的比较多了&#xff0c;“资本寒冬”。作为&#xff0c;一个在这个行业里干的人&#xff0c;真心是好怕怕呢。 妈蛋&#xff0c;真怕哪天就突然失业了呀。所以&#xff0c;无聊的(操蛋的)去整理了一些98年开始一直到现在的互联网中一些自己…

android oreo 开机动画,Android O添加了一个 Splash Screen API帮助简化启动屏制作

大多数开发者对闪屏(splash screen)都持不同的态度。一些人支持用闪屏隐藏app的加载&#xff0c;然后自然过渡到主界面&#xff0c;而另一些人认为闪屏不管是对用户还是开发者都是浪费时间。谷歌在这个问题上的立场也是不一的&#xff0c;以前不推荐使用闪屏的使用&#xff0c;…

拼接SQL的一个技巧

2019独角兽企业重金招聘Python工程师标准>>> 为了避免判断是否要在字符串中加 AND 来连接查询条件&#xff0c;我们可以直接在在WHERE 后面写 id>0。 这样&#xff0c;我们在拼接条件的时候&#xff0c;就可以直接写 AND 了。 SELECT* FROM t_table WHEREid>0…

【Linux导论】Linux引导流程(The Boot Process)

原文 LFS101x.2 Introduction to Linux (Linux Foundation) Chapter 03: Linux Structure and Installation - Section 2: The Boot Process 引导流程 - (The Boot Process) 你是否想过&#xff0c;在你按下电源开关直至Linux登陆提示出现时&#xff0c;后台到底发生了哪些事&a…

计算一行文本的高度

计算一行文本的高度 说明 有时候我们需要知道指定的几行文本的高度&#xff0c;此工具用于解决此种问题。 源码 // // NSStringLabelWidthAndHeight.h // ZiPeiYi // // Created by YouXianMing on 15/12/9. // Copyright © 2015年 YouXianMing. All rights reserved.…

用fputc()函数以字符串形式写入字符到磁盘文件

2019独角兽企业重金招聘Python工程师标准>>> #include <stdio.h> #include <stdlib.h>int main(){FILE *fp;char ch;if((fpfopen("testfile", "a")) NULL){fprintf(stderr, "Error opening file.\n",fp);exit(1);}print…

android 浮动文字提示,Android实现自由拖动并显示文字的悬浮框

项目中需要实现一个状态显示的悬浮框&#xff0c;要求可以设置两种模式&#xff1a;拖动模式和不可拖动模式。实现效果图如下&#xff1a;实现步骤&#xff1a;1.首先要设置该悬浮框的基本属性&#xff1a;/*** 显示弹出框** param context*/SuppressWarnings("WrongConst…

PHP的安装

PHP的环境也是诸多服务器软件的必要因素之一&#xff0c;它是一个HTML内嵌式语言&#xff0c;在服务器端执行。由于PHP的开源高效化平台&#xff0c;所以搭建一个php环境是一个运维工程师必备的能力。现在lamp也有类似lnmp.org那种一键安装包&#xff0c;地址是http://yumlamp.…

ntfs for mac使用注意事项有哪些?

2019独角兽企业重金招聘Python工程师标准>>> mac的用户有很多&#xff0c;一些用户朋友会发现自己的电脑是无法读写ntfs驱动器的。而ntfs驱动器又是一种常用的驱动器。面对这种情况我们可以选择用NTFS for Mac软件来帮助我们&#xff0c;它可以读写ntfs驱动器&#…

android音乐播放器文章,Android复习09【内容提供者、音乐播放器】

目 录PersonCpPersonCp.javainsert()ContentObserver音乐播放器1、添加读写权限1.1、动态权限授予(调用封装好的方法)2、获取音乐文件(MainActivity.java)2、Music.java(实体类)申请访问SD卡的权限设置适配器下拉刷新PersonCpPersonCp.javapackage cn.wangzg.personcp;import a…

程序员的业余项目

程序员的业余项目&#xff0c;我们也叫它 side project。 前几天&#xff0c;100offer 发起了一场活动叫 <寻找实干和坚持的技术力量>&#xff0c;他们是这么说的&#xff1a; 世界在被代码改变着&#xff0c;而我们在创造着代码。 仅仅是因为好玩&#xff0c;他开发了…

小米 android 8,小米华为们谁最良心?10大手机厂商安卓8.0升级情况盘点

3月8日&#xff0c;谷歌放出了首个安卓9.0开发者预览版的固件包&#xff0c;不出意外的话&#xff0c;它的正式版会在今年正式亮相。但对广大安卓用户来说&#xff0c;想要立刻用上最新系统并非易事。目前来说&#xff0c;安卓碎片化问题依然严重&#xff0c;我们不妨现实点&am…

窥探Swift之数组安全索引与数组切片

在Swift中的数组和字典中下标是非常常见的&#xff0c;数组可以通过索引下标进行元素的查询&#xff0c;字典可以通过键下标来获取相应的值。在使用数组时&#xff0c;一个常见的致命错误就是数组越界。如果在你的应用程序中数组越界了&#xff0c;那么对不起&#xff0c;如果由…

android tee,Android 9.0的新增安全特性与TEE

Android P&#xff0c;预计将于 2018 年第三季度发布最终版本。特别是Android8.0以来&#xff0c;安全性是Android版本变更的一个重要因素。从安全性增强方面来看&#xff0c;本次Android9.0版本主要有以下几个方面&#xff1a;统一的指纹身份验证对话框Android P 中&#xff0…

canny算子的理论分析

****************************************************************************************************************************************** 红&#xff1a;数字图像处理视频教程&#xff08;两部&#xff09; {中科院版36讲视频教程 电子科大版70讲视频教程&#x…

web框架-Struts开始

问题&#xff1a; 为什么有structs 作为一种框架&#xff08;frameset&#xff09;可以与传统的mvc进行比较&#xff1f; MVC是一种模式数据处理、显示和数据输入分开&#xff0c;来规范开发&#xff0c;但是却又并不规范。可以这样想&#xff1a;有三家公司&#xff0c;他们对…

接口自动化测试 返回html,接口自动化测试实战(更新完毕)

前言自动化没练习的项目怎么办&#xff1f;自动化已经成为测试的必备技能之一了&#xff0c;所以&#xff0c;很多想跳槽的测试朋友都在自学&#xff0c;特别是最实用的接口自动化&#xff0c;但是很多人因为没有可以练手的项目而苦恼&#xff0c;最终导致缺乏实战经验&#xf…