最终效果
看到这个设计时,只要你抓住关键点,其实就不难实现。注意观察,每次停留时刻度都是重合的,那么顺着这个思路,关键点应该是最终停留的位置。
想一想你之前见过的一些控件,有没有这样的
也许在这一不你可能步理解,再往下看
如果它长这样,是不是就恍然大悟,灵光闪现了。
只要把间隔和灰色区域宽度调整一下,是不是就是最终效果了。
现在只要找出控制灰色区域位置的方法,就大功告成了。
思路
-
UIScrollView
+ func scrollViewWillEndDragging(_: UIScrollView, withVelocity _: CGPoint, _: UnsafeMutablePointer<CGPoint>) {} -
UICollectionView
+ func scrollViewWillEndDragging(_: UIScrollView, withVelocity _: CGPoint, _: UnsafeMutablePointer<CGPoint>) {} -
UICollectionView
+CustomCollectionViewFlowLayout
有了思路,那就得找出最优选择,下面做详细分析。
UIScrollView
+ func scrollViewWillEndDragging(_: UIScrollView, withVelocity _: CGPoint, _: UnsafeMutablePointer<CGPoint>) {}
- 测试数量: 10000
- 卡顿: 由于构建视图个数太多,有明显延迟和卡顿
- 内存:8.4M 飙升到19.2M
- 定位精准度:良好
舍弃原因:性能太差
UICollectionView
+ func scrollViewWillEndDragging(_: UIScrollView, withVelocity _: CGPoint, _: UnsafeMutablePointer<CGPoint>) {}
- 测试数量: 10000
- 卡顿: 没有卡顿,滑动很流畅
- 内存:8.4M 降到5.6M
- 定位精准度:差
舍弃原因:定位精准度太差(也有可能是我处理有问题,那么做过多的特殊处理,显然不值得采用)
UICollectionView
+CustomCollectionViewFlowLayout
- 测试数量: 10000
- 卡顿: 没有卡顿,滑动很流畅
- 内存:8.4M 降到5.8M
- 定位精准度:良好
确定最优方案后,那么下一步就是完善功能了。
第一步:
去掉多余间隔
layout.minimumLineSpacing = 0
第二步:
改变刻度线高度
var index: Int = 0 {
didSet {
if index % 5 == 0 {
line.frame.size.height = flat(frame.height * goldenRatio)
} else {
line.frame.size.height = flat(frame.height * goldenRatio * goldenRatio)
}
line.frame.origin.y = frame.height - line.frame.height
}
}
通过观察,我们知道尺子较长刻度一般是5的倍数,所以只需要对外开一个index
的接口即可,不用暴露内部实现。
外部使用(cell as? MootsCollectionCell)?.index = indexPath.row
在这里为了视觉上的美感,我使用了两个黄金比例:
- 长刻度和cell高度
- 长刻度和短刻度
但是这会带来一个新问题,就是计算出来的值不为最小像素时,会带来性能问题,在这里不做解释。可跳转
那么用下面方法处理一下即可
// plus 0.333333... pt = 1px, 视网膜屏幕 0.5pt = 1px
func flat(_ value: CGFloat) -> CGFloat {
let s = UIScreen.main.scale
return ceil(value * s) / s
}
第三步:
加上数字
var index: Int = 0 {
didSet {
textLabel?.removeFromSuperview()
if index % 5 == 0 {
textLabel = UILabel()
textLabel?.font = UIFont.systemFont(ofSize: UIFont.systemFontSize)
textLabel?.text = "\(index * 100)"
textLabel?.sizeToFit()
textLabel?.center.x = line.frame.minX
contentView.addSubview(textLabel!)
line.frame.size.height = flat(frame.height * goldenRatio)
} else {
line.frame.size.height = flat(frame.height * goldenRatio * goldenRatio)
}
line.frame.origin.y = frame.height - line.frame.height
}
}
第四步:
画底部线
我一开始的想法,是每个cell
中画一条,然后在UICollectionView
的前后各画一条。但是显然这不仅复杂,而且不那么nice。
其实只需要在RulerView
的底部画一条即可啊,反正它是不需要动的
第五步:
显示当前刻度值
我开始的思路是,滚动停止时,拿出显示的cell
居中的那个。
结果是这样的
显然思路是错误的,根本无法定位到中对齐的那个cell。
那么能不能通过frame
或者别的什么位置直接计算出呢?
突然想到了这个contentOffset
,这个应该很熟悉了吧。
打印一下
选中刻度分别为2、1、0,他们差值刚好为cell
宽度诶,16。
再看看刻度为零时
let margin = (self.frame.width - itemWidth) / 2
collectionView.contentInset = UIEdgeInsets(top: 0, left: margin, bottom: 0, right: margin)
这不刚好就是调整contentInset
的值吗。
所以有
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let index = (scrollView.contentOffset.x + scrollView.contentInset.left) / itemWidth
textLabel.alpha = 0
UIView.animate(withDuration: 0.25) {
self.textLabel.alpha = 1
// 这里有次显示为负数,做一下特殊处理
self.textLabel.text = String(format: "%.2f", max(Double(index * 100), 0))
}
rulerViewDelegate?.didSelectItem(with: Int(index))
}
完美,大功告成
尾巴
如果你对UIScrollView
不熟悉,请一定看看这篇文章,我在里面学到了很多东西。
如果你对自定义UICollectionViewLayout
不熟,看看这个库。或者这个