我的思路是用 UICollectionView ,定义 100 组(每组就是那三张图片),然后当向左滑动到头的时候或者向右滑动到头的时候,定位到中间 50 组数据的部分,但是这样会有一个切换效果:就是当你向左滑动,本来动画是向左滑动的动画,但是当向左滑动到头的时候,他会向右切换到中间的位置,同样的向右滑动也是这样的道理,导致有明显的切换效果,大家有什么解决方案吗
或者采用其他什么思路实现左右循环滚动丝滑的切换
要实现的效果图; http://cdn.tudoutiao.pro/2023-09-07%2011.23.28.gif
我的仓库地址: https://github.com/tudoutiaoya/ScrollPicture
下面是我的代码
import UIKit
class HorizontalRollViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
let collectionView: UICollectionView
let images = ["image1", "image2", "image3"]
let layout: UICollectionViewFlowLayout
var myOffsetX = 0.0 // 记录上次的 offsetx 便于判断是左滑还是右滑
let groupNum = 100 // 定义多少个组
let lineSpacing = 30.0
let itemWidth = UIScreen.main.bounds.width/2 // 卡片宽度
let itemHeigh = UIScreen.main.bounds.height/2
init() {
layout = UICollectionViewFlowLayout()
layout.minimumLineSpacing = self.lineSpacing
layout.itemSize = CGSize(width: itemWidth, height: itemHeigh)
layout.scrollDirection = .horizontal
collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
collectionView.decelerationRate = .fast
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
view.backgroundColor = .white
setupSubView()
// 初始定位到中间
collectionView.scrollToItem(at: IndexPath.init(item: groupNum/2 * images.count , section: 0), at: .centeredHorizontally, animated: false)
}
func setupSubView() {
collectionView.frame = view.bounds
collectionView.dataSource = self
collectionView.delegate = self
collectionView.isPagingEnabled = false
// 注册单元格
collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "UICollectionViewCell")
view.addSubview(collectionView)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
// 图片的数量
return groupNum * images.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "UICollectionViewCell", for:indexPath)
// 移除之前的子视图
cell.contentView.subviews.forEach { $0.removeFromSuperview() }
// 取余 计算出应该在 images 数组哪个位置
let imageIndex = indexPath.item % images.count
let imageView = UIImageView(image: UIImage(named: images[imageIndex]))
imageView.frame = cell.bounds
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
cell.contentView.addSubview(imageView)
return cell
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
// 停止滑动时,当前的偏移量(即最近停止的位置)
self.myOffsetX = scrollView.contentOffset.x
}
// collectionView.pagingEnabled = NO;
// 禁止分页滑动时,根据偏移量判断滑动到第几个 item
// 滑动 “减速滚动时” 是触发的代理,当用户用力滑动或者清扫时触发
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
self.scrollToNextPageOrLastPage(scrollView)
}
// 用户拖拽时 调用
func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
self.scrollToNextPageOrLastPage(scrollView)
}
func scrollToNextPageOrLastPage(_ scrollView: UIScrollView) {
// 到达左右边界,定位到中间
let contentWidth = (itemWidth+lineSpacing) * Double(groupNum*images.count) // 内容的总宽度
let adjustedContentWidth = contentWidth - lineSpacing // 调整后的内容宽度,减去最后一个间距
let rightOffset = adjustedContentWidth - scrollView.bounds.width // 右侧边界的偏移量
if (scrollView.contentOffset.x >= rightOffset || scrollView.contentOffset.x <= 0) {
collectionView.scrollToItem(at: IndexPath.init(item: groupNum/2 * images.count , section: 0), at: .centeredHorizontally, animated: false)
print("切换了")
return
}
// 之前停止的位置,判断左滑、右滑
if (scrollView.contentOffset.x > self.myOffsetX) { // 左滑,下一个( i 最大为 cell 个数)
// 计算移动的 item 的个数( item.width + 间距)
let i = Int(scrollView.contentOffset.x / (itemWidth + lineSpacing) + 1)
let indexPath = IndexPath(row: i, section: 0)
// item 居中显示
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
} else { // 右滑,上一个( i 最小为 0 )
let i = Int(scrollView.contentOffset.x / (itemWidth + lineSpacing) + 1)
let indexPath = IndexPath(row: i, section: 0)
collectionView.scrollToItem(at: indexPath, at: .centeredHorizontally, animated: true)
}
}
}
1
wenjor 2023-09-07 17:41:02 +08:00
考虑下直接写动画?
|
2
Chang12 2023-09-07 17:51:29 +08:00
FSPagerView
|
3
iOCZ 2023-09-07 18:17:57 +08:00
无限滚动已经出现那么多年了,思路无非就是你说的这种。如果是定时器自动滚动的话,每次都滚动中间那组就行了。如果是手动触发的话,向左向右滚动到尽头应该还是比较累的。
|
4
iOCZ 2023-09-07 18:32:22 +08:00
讲道理,当你不同滚动的时候,你无法偷偷换到中间的那组,一般会选择滚动结束的时候偷偷把 offset 设置回去
|
5
iyeatse 2023-09-07 18:43:39 +08:00
怎么弄这么麻烦,scrollViewWillEndDragging 的第三个参数是个指针,可以修改的
|
6
tudoutiaoya OP @iOCZ 能详细讲讲不佬,刚学 iOS😭
|
7
iOCZ 2023-09-07 20:04:22 +08:00
@tudoutiaoya 我说的这个是 SDCycleScrollView 的实现思路,不过他似乎没处理你这种手工滚到头的情况,你可以看看源码。。。我看你这个没定时器
|
8
V2SuperUser 2023-09-08 11:34:31 +08:00
根据 https://juejin.cn/post/6940140043042291748 的缩放效果改了一个,你试试效果
```Swift import UIKit class ViewController: UIViewController { private let margin: CGFloat = 20 private var itemW: CGFloat = .zero private let cellID = "baseCellID" private let cellCount = 100 private var collectionView: UICollectionView! override func viewDidLoad() { super.viewDidLoad() setUpView() } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() } func setUpView() { let layout = ZGFlowLayout() let collH: CGFloat = 200 let itemH = collH - margin * 2 itemW = view.bounds.width - margin * 2 - 100 layout.itemSize = CGSize(width: itemW, height: itemH) layout.minimumLineSpacing = margin layout.scrollDirection = .horizontal collectionView = UICollectionView(frame: CGRect(x: 0, y: 180, width: view.bounds.width, height: collH), collectionViewLayout: layout) collectionView.backgroundColor = .black collectionView.showsHorizontalScrollIndicator = false collectionView.dataSource = self collectionView.delegate = self collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: cellID) view.addSubview(collectionView) scrollTo(index: cellCount/2) } } extension ViewController: UICollectionViewDelegate, UICollectionViewDataSource{ func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cellCount } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellID, for: indexPath) let colors: [UIColor] = [.red, .yellow, .blue] cell.backgroundColor = colors[indexPath.item % 3] return cell } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { let scrollStop = !scrollView.isTracking && !scrollView.isDragging && !scrollView.isDecelerating guard scrollStop else { return } ZGScrollViewDidEndScroll(scrollView: scrollView) } func scrollViewDidScroll(_ scrollView: UIScrollView) { //防止滚动到最后或者最前,如果觉得改变时突兀,可以增加 cellCount let page = getCurrentPage(scrollView: scrollView) if page >= 95 || page <= 5 { scrollTo(index: page%3 + cellCount+1) } } private func ZGScrollViewDidEndScroll(scrollView: UIScrollView) { let page = getCurrentPage(scrollView: scrollView) scrollTo(index: page%3 + cellCount+1) } private func scrollTo(index: Int){ collectionView.scrollToItem(at: IndexPath(item: index, section: 0), at: .centeredHorizontally, animated: false) } private func getCurrentPage(scrollView: UIScrollView) -> Int{ //第一个 page 偏移量会少了多显示出来的一半,不使用之后的计算,直接判定为 0 var page: CGFloat = 0 if scrollView.contentOffset.x > 0 { //计算单次滑动的偏移量 let scrollW = scrollView.frame.width //显示的多出一半的宽度 let half = (scrollW - itemW)/2 //除第一个外其余每次滑动的偏移量 let eachOffset = (itemW+margin) //第一个 cell 的偏移量 let firstOffset = eachOffset-half page = (scrollView.contentOffset.x-firstOffset)/(eachOffset) + 1 } return Int(page) } } class ZGFlowLayout: UICollectionViewFlowLayout { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { let attributes = super.layoutAttributesForElements(in: rect) let centerX = collectionView!.contentOffset.x + collectionView!.bounds.width / 2 attributes?.forEach({ (attr) in let pad = abs(centerX - attr.center.x) let factor = 0.0009 let scale = 1 / (1 + pad * CGFloat(factor)) attr.transform = CGAffineTransform(scaleX: scale, y: scale) }) return attributes } override func targetContentOffset(forProposedContentOffset proposedContentOffset: CGPoint, withScrollingVelocity velocity: CGPoint) -> CGPoint { var targetPoint = proposedContentOffset let centerX = proposedContentOffset.x + collectionView!.bounds.width / 2 let attrs = self.layoutAttributesForElements(in: CGRect(x: proposedContentOffset.x, y: proposedContentOffset.y, width: collectionView!.bounds.size.width, height: collectionView!.bounds.size.height)) var moveDistance: CGFloat = CGFloat(MAXFLOAT) attrs!.forEach { (attr) in if abs(attr.center.x - centerX) < abs(moveDistance) { moveDistance = attr.center.x - centerX } } if targetPoint.x > 0 && targetPoint.x < collectionViewContentSize.width - collectionView!.bounds.width { targetPoint.x += moveDistance } return targetPoint } override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool { return true } override var collectionViewContentSize: CGSize { return CGSize(width: sectionInset.left + sectionInset.right + (CGFloat(collectionView!.numberOfItems(inSection: 0)) * (itemSize.width + minimumLineSpacing)) - minimumLineSpacing, height: 0) } } ``` |
9
V2SuperUser 2023-09-08 11:36:47 +08:00
@V2SuperUser 为啥我的代码不能像 OP 一样有格式
|
10
V2SuperUser 2023-09-08 11:40:56 +08:00
@V2SuperUser 修正:两处 scrollTo(index: page%3 + cellCount+1)改为 scrollTo(index: page%3 + cellCount/2+1)
|
11
V2SuperUser 2023-09-08 12:10:20 +08:00
|