
我的思路是用 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) -> UICllectionViewCell { 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 |