概述

UICollectionView真的好强大,今天我们来研究一下这种很常见的卡片动画效果是如何实现了。本篇不能太深入地讲解,因为笔者也是刚刚摸索出点眉目,但是并没有深刻地理解。如果在讲解过程中,出现不对的地方,请及时反馈。

效果图

重写API

 
1
2
3
4
5
6
7
8
9
10
11
12
 
// 我们必须重写此方法,指定布局大小
// 每次layout invalidated或者重新query布局信息时,会调用
- (void)prepareLayout;
 
// 用于决定布局信息
// 我们必须重写它来实现布局信息
- (nullable NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect;
 
// 重写它来布局信息
- (nullable UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath;
 

还有一个非常关键的API,必须重写:

 
1
2
3
4
5
 
// return YES to cause the collection view to requery the layout for geometry information
// 当重新查询布局信息时,就会调用此API。要设置为YES,才能实现自定义布局。
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds;
 

自定义布局

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
 
//
//  HYBCardFlowLayout.m
//  CollectionViewDemos
//
//  Created by huangyibiao on 16/3/26.
//  Copyright © 2016年 huangyibiao. All rights reserved.
//
 
#import "HYBCardFlowLayout.h"
 
@interface HYBCardFlowLayout ()
 
@property (nonatomic, strong) NSIndexPath *mainIndexPath;
@property (nonatomic, strong) NSIndexPath *willMoveToMainIndexPath;
 
@end
 
@implementation HYBCardFlowLayout
 
- (void)prepareLayout {
  CGFloat inset = 32;
  self.itemSize = CGSizeMake(self.collectionView.frame.size.width - 2 * inset,
                             self.collectionView.frame.size.height * 3 / 4);
  self.sectionInset = UIEdgeInsetsMake(0, inset, 0, inset);
  self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
  
  [super prepareLayout];
}
 
#pragma mark - Override
- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
  return YES;
}
 
- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
  UICollectionViewLayoutAttributes *attribute = [super layoutAttributesForItemAtIndexPath:indexPath];
  
  [self setTransformForLayoutAttributes:attribute];
  
  return attribute;
}
 
- (NSArray<UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect {
  NSArray *attributesSuper = [super layoutAttributesForElementsInRect:rect];
  // 一定要深复制一份,不能修改父类的属性,不然会有很多error打印出来
  NSArray *attributes = [[NSArray alloc] initWithArray:attributesSuper copyItems:YES];
  
  NSArray *visibleIndexPaths = [self.collectionView indexPathsForVisibleItems];
  
  if (visibleIndexPaths.count <= 0) {
    return attributes;
  } else if (visibleIndexPaths.count == 1) {
    self.mainIndexPath = [visibleIndexPaths firstObject];
    self.willMoveToMainIndexPath = nil;
  } else if (visibleIndexPaths.count == 2) {
    NSIndexPath *indexPath = [visibleIndexPaths firstObject];
    
    // 说明是往左滚动
    if (indexPath == self.mainIndexPath) {
      // 记录将要移进来的位置
      self.willMoveToMainIndexPath = visibleIndexPaths[1];
    } else {// 往右滚动
      self.willMoveToMainIndexPath = visibleIndexPaths[0];
      
      // 更新下一个成为main
      self.mainIndexPath = visibleIndexPaths[1];
    }
  }
  
  for (UICollectionViewLayoutAttributes *attribute in attributes) {
    [self setTransformForLayoutAttributes:attribute];
  }
  
  return attributes;
}
 
- (void)setTransformForLayoutAttributes:(UICollectionViewLayoutAttributes *)attribute {
  UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:attribute.indexPath];
  
  if (self.mainIndexPath && attribute.indexPath.section == self.mainIndexPath.section) {
    attribute.transform3D = [self tranformForView:cell];
  } else if (self.willMoveToMainIndexPath && attribute.indexPath.section == self.willMoveToMainIndexPath.section) {
    attribute.transform3D = [self tranformForView:cell];
  }
}
 
- (CATransform3D)tranformForView:(UICollectionViewCell *)view {
  // cell的起始偏移
  CGFloat w = self.collectionView.frame.size.width;
  CGFloat offset = [self.collectionView indexPathForCell:view].section * w;
  
  // 当前偏移
  CGFloat currentOffset = self.collectionView.contentOffset.x;
  
  // 计算偏移angle
  CGFloat angle = (currentOffset - offset) / w;
  
  CATransform3D t = CATransform3DIdentity;
  t.m34 = 1.0 / -500;
  
  if (currentOffset - offset >= 0) {
    t = CATransform3DRotate(t, angle, 1, 1, 0);
  } else {
    t = CATransform3DRotate(t, angle, -1, 1, 0);
  }
  
  return t;
}
 
@end
 

这里主要是要处理旋转。然后要处理切换cell的attribute设置。mainIndexPath属性用于记录当前显示的cell的位置。willMoveToMainIndexPath记录将要出现的cell的位置。

结尾

这里在慢慢切换时,效果是挺好的,但是如果快速切换卡片,你会发现会有一点点不好之处,就是下一个cell突然出现的。

CollectionView旋转水平卡片布局的更多相关文章

  1. CollectionView缩放水平卡片布局

    实现效果 实现思路 从Demo效果图中,可以看出来,主要是缩放系数的计算.对于不同距离的cell,其缩放系数要变化,以便整体协调显示. 所以,我们必须重写-layoutAttributesForEle ...

  2. CollectionView垂直缩放卡片布局

    实现效果 实现思路 从效果图可以看到变化是,越是往中间滚动的item显示最大,越显眼.而越是往前面,或者越是后面的,反而显示越小,这样就形成了视觉差. 实现的思路就是通过重写在可见范围内的所有item ...

  3. Java基础之创建窗口——使用卡片布局管理器(TryCardLayout)

    控制台程序. 卡片布局管理器会生成一叠组件——一个组件放在另一个组件的上面.添加到容器中的第一个组件在堆栈的顶部,因此是可见的,添加的最后一个组件在堆栈的底部.使用默认的构造函数CardLayout( ...

  4. 转:三十二、Java图形化界面设计——布局管理器之CardLayout(卡片布局)

    转:http://blog.csdn.net/liujun13579/article/details/7773945 卡片布局能够让多个组件共享同一个显示空间,共享空间的组件之间的关系就像一叠牌,组件 ...

  5. 三十二、Java图形化界面设计——布局管理器之CardLayout(卡片布局)

    摘自 http://blog.csdn.net/liujun13579/article/details/7773945 三十二.Java图形化界面设计--布局管理器之CardLayout(卡片布局) ...

  6. 布局管理器之CardLayout(卡片布局管理器)

    对于选项卡这个概念大家可能不会陌生,就是在一个窗口中可以切换显示多页不同的内容,但同一时间只能是其中的某一页可见的,这样的一个个的页面就是选项卡. CardLayout就是类似的这样一个布局管理器,它 ...

  7. 技术胖Flutter第三季-18布局CardWidget 卡片布局组件

    技术胖Flutter第三季-18布局CardWidget 卡片布局组件 博客地址: https://jspang.com/post/flutter3.html#toc-420 最外面是Card布局,里 ...

  8. 慕课网5-2编程练习:flex布局制作卡片布局案例

    慕课网5-2编程练习:flex布局制作卡片布局案例 小伙伴们,学习了卡片布局,接下来我们根据效果图,也写出一个卡片布局的页面吧! 效果图如下: 任务 1.主体内容的卡片一行只能显示两个. 2.卡片与卡 ...

  9. 设备旋转,创建水平模式布局--Android studio

    1.在项目工具窗口中,右键单击res目录后选择new--Android resource directory菜单项. 2.从资源类型Resource type列表中选择layout,保持Source ...

随机推荐

  1. HDU-2159 FATE,01背包变形

    FATE 这道题和完全背包十分类似,只不过加上了忍耐度这个条件限制,所以很正常的想到用三维数组来模拟.但背包问题优化只有一层循环,这里当然也可以把种类这一层省略.d[i][j]表示杀i只怪耗费忍耐度为 ...

  2. [codevs1050]棋盘染色 2

    [codevs1050]棋盘染色 2 试题描述 有一个5*N的棋盘,棋盘中的一些格子已经被染成了黑色,你的任务是对最少的格子染色,使得所有的黑色能连成一块. 输入 第一行一个整数N(<=100) ...

  3. POJ 1523 网络连通

    题目大意: 给你一个网络组,每台机子与其他机子的关系,让你找到所有的割点,如果没有割点,输出无 这道题目就是最直接的求割点问题,我在这里用的是邻接矩阵来存储机子之间的关系 割点问题的求解需要对深度优先 ...

  4. 微服务架构的基础框架选择:Spring Cloud还是Dubbo?

    本文转自:http://mt.sohu.com/20160803/n462486707.shtml 最近一段时间不论互联网还是传统行业,凡是涉及信息技术范畴的圈子几乎都在讨论 微服务架构 .近期也看到 ...

  5. C# 通过T4自动生成代码

    通过T4模板生成代码,运行时实现 关键代码段:Host using Microsoft.VisualStudio.TextTemplating; using System; using System. ...

  6. HDU 5360 【优先队列+贪心】

    题意: 给定N个无序区间. 对合法区间的定义是: 在这个区间之前已经选出了至少l个合法区间,最多选出了r个合法区间.则该区间为合法区间. 输出最多能挑选出多少个合法区间,并输出合法区间的数量. 思路: ...

  7. linux命名详解及其软件安装实例

    始于cd,ls命令 好啦,步入正题,我使用的linux连接工具为xshell,mRemoteNG,对两款工具不做介绍啦,你可以百度一下,实在不会入左上方群. 进入之后,便是上面的界面黑乎乎一片,对于初 ...

  8. Java的finally语句在try或catch中的return语句执行之后还是之前?

    import java.util.HashMap; import java.util.Map; public class FinallyDemo1 { public static void main( ...

  9. 第三方APP集成微信登陆功能详解

    授权后接口调用(UnionID) 通过code获取access_token 接口说明 通过code获取access_token的接口. 请求说明 http请求方式: GET https://api.w ...

  10. 各种Js插件汇总;JavaScript插件

    1.jquery信息提示插件: https://blog.csdn.net/u013517229/article/details/78291841 http://www.jqueryfuns.com/ ...