1,效果图
(1)图片从左至右横向排列(只有一行),通过手指拖动可以前后浏览图片。
(2)视图滚动时,每张图片根据其与屏幕中心距离的不同,显示尺寸也会相应地变化。越靠近屏幕中心尺寸就越大,远离屏幕中心的就逐渐变小。
(3)滑动结束后,会有位置自动修正的功能。即将当前最靠近屏幕中点的图片移动到正中央。
(4)点击图片则将该图片删除,点击空白处会在最开始的位置插入一张图片。不管新增还是删除都有动画效果。
(5)点击导航栏上的“
切换”按钮,可以在普通的流式布局和我们自定义的画廊布局间相互切换。切换时也是有动画效果的。
2,画廊布局类:LinearCollectionViewLayout
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
|
import
UIKit
class
LinearCollectionViewLayout
:
UICollectionViewFlowLayout
{
//元素宽度
var
itemWidth:
CGFloat
= 100
//元素高度
var
itemHeight:
CGFloat
= 100
//对一些布局的准备操作放在这里
override
func
prepare() {
super
.prepare()
//设置元素大小
self
.itemSize =
CGSize
(width: itemWidth, height: itemHeight)
//设置滚动方向
self
.scrollDirection = .horizontal
//设置间距
self
.minimumLineSpacing =
self
.collectionView!.bounds.width / 2 - itemWidth
//设置内边距
//左右边距为了让第一张图片与最后一张图片出现在最中央
//上下边距为了让图片横行排列,且只有一行
let
left = (
self
.collectionView!.bounds.width - itemWidth) / 2
let
top = (
self
.collectionView!.bounds.height - itemHeight) / 2
self
.sectionInset =
UIEdgeInsetsMake
(top, left, top, left)
}
//边界发生变化时是否重新布局(视图滚动的时候也会触发)
//会重新调用prepareLayout和调用
//layoutAttributesForElementsInRect方法获得部分cell的布局属性
override
func
shouldInvalidateLayout(forBoundsChange newBounds:
CGRect
) ->
Bool
{
return
true
}
//rect范围下所有单元格位置属性
override
func
layoutAttributesForElements(
in
rect:
CGRect
)
-> [
UICollectionViewLayoutAttributes
]? {
//从父类得到默认的所有元素属性
let
array =
super
.layoutAttributesForElements(
in
: rect)
//可见区域(目前显示出来的位于collection view上的矩形区域)
let
visiableRect =
CGRect
(x:
self
.collectionView!.contentOffset.x,
y:
self
.collectionView!.contentOffset.y,
width:
self
.collectionView!.frame.width,
height:
self
.collectionView!.frame.height)
//当前屏幕中点,相对于collect view上的x坐标
let
centerX =
self
.collectionView!.contentOffset.x
+
self
.collectionView!.bounds.width / 2
//这个是为了计算缩放比例的
let
maxDeviation =
self
.collectionView!.bounds.width / 2 + itemWidth / 2
for
attributes
in
array! {
//与可见区域做碰撞,如果该单元格没显示则直接跳过
if
!visiableRect.intersects(attributes.frame) {
continue
}
//显示的单元根据偏移量决定放大倍数(最大放大1.8倍,而离屏幕中央越远的单元格缩放的越小)
let
scale = 1 + (0.8 - abs(centerX - attributes.center.x) / maxDeviation)
attributes.transform =
CGAffineTransform
(scaleX: scale, y: scale)
}
return
array
}
/**
用来设置collectionView停止滚动那一刻的位置(实现目的是当停止滑动,时刻有一张图片是位于屏幕最中央的)
proposedContentOffset: 原本collectionView停止滚动那一刻的位置
velocity:滚动速度
返回:最终停留的位置
*/
override
func
targetContentOffset(forProposedContentOffset
proposedContentOffset:
CGPoint
, withScrollingVelocity velocity:
CGPoint
) ->
CGPoint
{
//停止滚动时的可见区域
let
lastRect =
CGRect
(x: proposedContentOffset.x, y: proposedContentOffset.y,
width:
self
.collectionView!.bounds.width,
height:
self
.collectionView!.bounds.height)
//当前屏幕中点,相对于collect view上的x坐标
let
centerX = proposedContentOffset.x +
self
.collectionView!.bounds.width * 0.5;
//这个可见区域内所有的单元格属性
let
array =
self
.layoutAttributesForElements(
in
: lastRect)
//需要移动的距离
var
adjustOffsetX =
CGFloat
(
MAXFLOAT
);
for
attri
in
array! {
//每个单元格里中点的偏移量
let
deviation = attri.center.x - centerX
//保存偏移最小的那个
if
abs(deviation) < abs(adjustOffsetX) {
adjustOffsetX = deviation
}
}
//通过偏移量返回最终停留的位置
return
CGPoint
(x: proposedContentOffset.x + adjustOffsetX, y: proposedContentOffset.y)
}
}
|
3,使用样例
(1)自定义单元格类:
MyCollectionViewCell.swift(创建的时候生成对应的 xib 文件)
1
2
3
4
5
6
7
8
9
10
11
12
|
import
UIKit
//自定义的Collection View单元格
class
MyCollectionViewCell
:
UICollectionViewCell
{
//用于显示图片
@IBOutlet
weak
var
imageView:
UIImageView
!
override
func
awakeFromNib() {
super
.awakeFromNib()
}
}
|
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
113
114
115
116
117
118
119
120
121
122
|
import
UIKit
class
ViewController
:
UIViewController
{
//普通的flow流式布局
var
flowLayout:
UICollectionViewFlowLayout
!
//自定义的线性布局
var
linearLayput:
LinearCollectionViewLayout
!
var
collectionView:
UICollectionView
!
//重用的单元格的Identifier
let
CellIdentifier
=
"myCell"
//所有书籍数据
var
images = [
"c#.png"
,
"html.png"
,
"java.png"
,
"js.png"
,
"php.png"
,
"react.png"
,
"ruby.png"
,
"swift.png"
,
"xcode.png"
]
override
func
viewDidLoad() {
super
.viewDidLoad()
//初始化Collection View
initCollectionView()
//注册tap点击事件
let
tapRecognizer =
UITapGestureRecognizer
(target:
self
,
action: #selector(
ViewController
.handleTap(_:)))
collectionView.addGestureRecognizer(tapRecognizer)
}
private
func
initCollectionView() {
//初始化flow布局
flowLayout =
UICollectionViewFlowLayout
()
flowLayout.itemSize =
CGSize
(width: 60, height: 60)
flowLayout.sectionInset =
UIEdgeInsets
(top: 74, left: 0, bottom: 0, right: 0)
//初始化自定义布局
linearLayput =
LinearCollectionViewLayout
()
//初始化Collection View
collectionView =
UICollectionView
(frame: view.bounds,
collectionViewLayout: linearLayput)
//Collection View代理设置
collectionView.delegate =
self
collectionView.dataSource =
self
collectionView.backgroundColor = .white
//注册重用的单元格
let
cellXIB =
UINib
.
init
(nibName:
"MyCollectionViewCell"
, bundle:
Bundle
.main)
collectionView.register(cellXIB, forCellWithReuseIdentifier:
CellIdentifier
)
//将Collection View添加到主视图中
view.addSubview(collectionView)
}
//点击手势响应
func
handleTap(_ sender:
UITapGestureRecognizer
){
if
sender.state ==
UIGestureRecognizerState
.ended{
let
tapPoint = sender.location(
in
:
self
.collectionView)
//点击的是单元格元素
if
let
indexPath =
self
.collectionView.indexPathForItem(at: tapPoint) {
//通过performBatchUpdates对collectionView中的元素进行批量的插入,删除,移动等操作
//同时该方法触发collectionView所对应的layout的对应的动画。
self
.collectionView.performBatchUpdates({ () ->
Void
in
self
.collectionView.deleteItems(at: [indexPath])
self
.images.remove(at: indexPath.row)
}, completion:
nil
)
}
//点击的是空白位置
else
{
//新元素插入的位置(开头)
let
index = 0
images.insert(
"xcode.png"
, at: index)
self
.collectionView.insertItems(at: [
IndexPath
(item: index, section: 0)])
}
}
}
//切换布局样式
@IBAction
func
changeLayout(_ sender:
Any
) {
self
.collectionView.collectionViewLayout.invalidateLayout()
//交替切换新布局
let
newLayout = collectionView.collectionViewLayout
.isKind(of:
LinearCollectionViewLayout
.
self
) ? flowLayout : linearLayput
collectionView.setCollectionViewLayout(newLayout!, animated:
true
)
}
override
func
didReceiveMemoryWarning() {
super
.didReceiveMemoryWarning()
}
}
//Collection View数据源协议相关方法
extension
ViewController
:
UICollectionViewDataSource
{
//获取分区数
func
numberOfSections(
in
collectionView:
UICollectionView
) ->
Int
{
return
1
}
//获取每个分区里单元格数量
func
collectionView(_ collectionView:
UICollectionView
,
numberOfItemsInSection section:
Int
) ->
Int
{
return
images.count
}
//返回每个单元格视图
func
collectionView(_ collectionView:
UICollectionView
,
cellForItemAt indexPath:
IndexPath
) ->
UICollectionViewCell
{
//获取重用的单元格
let
cell = collectionView.dequeueReusableCell(withReuseIdentifier:
CellIdentifier
,
for
: indexPath)
as
!
MyCollectionViewCell
//设置内部显示的图片
cell.imageView.image =
UIImage
(named: images[indexPath.item])
return
cell
}
}
//Collection View样式布局协议相关方法
extension
ViewController
:
UICollectionViewDelegate
{
}
|
![](https://i-blog.csdnimg.cn/blog_migrate/b21d9ae556170caacaaa2b785159716c.gif)
原文出自:www.hangge.com 转载请保留原文链接:http://www.hangge.com/blog/cache/detail_1602.html