按照用户方向旋转collectionView
Posted
技术标签:
【中文标题】按照用户方向旋转collectionView【英文标题】:Rotate collectionView in circle following user direction 【发布时间】:2019-01-31 21:04:46 【问题描述】:我正在尝试使用循环器布局创建collectionView,并且我希望collectionView 在用户以任何方向在屏幕上滑动手指时旋转。我发现collectionView的圆形布局是我到目前为止所做的
为了旋转这个collectionView,我写了这段代码
为collectionView添加手势
panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.gestureReader(_:)))
panGesture.cancelsTouchesInView = false
self.collectionView.addGestureRecognizer(panGesture)
这里是gestureReader和animation方法
@objc private func gestureReader(_ gesture: UIPanGestureRecognizer)
var startLocation = CGPoint.zero
var endLocation = CGPoint.zero
let currentLocation = gesture.location(in: self.collectionView)
if gesture.state == .began
startLocation = currentLocation
if gesture.state == .ended
endLocation = currentLocation
self.startRotatingView(start: startLocation, end: endLocation)
private func startRotatingView(start:CGPoint, end: CGPoint)
let dx = end.x - start.x
let dy = end.y - start.y
let distance = abs(sqrt(dx*dx + dy*dy))
print(distance)
if start.x > end.x
if start.y > end.y
//positive value of pi
self.circleAnimation(-distance)
else
//negitive value of pi
self.circleAnimation(distance)
else
if start.y > end.y
//positive value of pi
self.circleAnimation(-distance)
else
//negitive value of pi
self.circleAnimation(distance)
private func circleAnimation(_ angle:CGFloat)
UIView.animate(withDuration: 0.7, delay: 0, options: .curveLinear, animations:
self.collectionView.transform = CGAffineTransform.identity
self.collectionView.transform = CGAffineTransform.init(rotationAngle: angle)
) (true) in
//
首先动画无法正常工作,其次当 collectionView 旋转时,这就是我得到的
问题1:我还需要添加什么才能使这个动画流畅并跟随用户的手指? 问题2:我想让collectionViewcells保持和之前一样的动画,我该如何实现呢,请帮忙
提前致谢
【问题讨论】:
使用集合循环布局和动态项目行为将很容易解决您的问题 能否请您用一些代码解释或分享任何演示源 developer.apple.com/library/archive/documentation/WindowsViews/… 【参考方案1】:我在这里给你看一个例子。装饰视图 S1View 是 UICollectionViewCell 的子类,标识符为“background”。
代码不难理解,但组合起来很乏味。如何控制动画师是另一回事。
class TestCollectionViewLayout: UICollectionViewLayout
lazy var dataSource : UICollectionViewDataSource? =
self.collectionView?.dataSource
()
var layouts : [IndexPath: UICollectionViewLayoutAttributes?] = [:]
var itemNumber : Int
return dataSource!.collectionView(collectionView!, numberOfItemsInSection: 0)
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?
var itemArray = (0..<itemNumber).map self.layoutAttributesForItem(at: IndexPath.init(row: $0, section: 0))!
itemArray.append(self.layoutAttributesForDecorationView(ofKind:"background"
, at: IndexPath.init(row: 0, section: 0)))
return itemArray
override var collectionViewContentSize: CGSize get
return self.collectionView?.frame.size ?? CGSize.zero
lazy var dynamicAnimator = UIDynamicAnimator(collectionViewLayout: self)()
private func updateCurrentLayoutAttributesForItem(at indexPath: IndexPath, current: UICollectionViewLayoutAttributes?) -> UICollectionViewLayoutAttributes?
return current
private func initLayoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
let layoutAttributes = UICollectionViewLayoutAttributes(forCellWith: indexPath)
let center = (collectionView?.center)!
let angle = (CGFloat(indexPath.row) / CGFloat(itemNumber) * CGFloat.pi * 2)
layoutAttributes.center = CGPoint.init(x: center.x + cos(angle) * CGFloat(radius) , y: center.y + sin(angle) * CGFloat(radius) )
layoutAttributes.bounds = CGRect.init(x: 0, y: 0, width: 100, height: 100 )
if let decorator = self.decorator
let itemBehavior =
UIAttachmentBehavior.pinAttachment(with: layoutAttributes, attachedTo: decorator, attachmentAnchor: layoutAttributes.center)
dynamicAnimator.addBehavior(itemBehavior)
layouts[indexPath] = layoutAttributes
return layoutAttributes
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes?
guard let currentLayout = layouts[indexPath] else
return initLayoutAttributesForItem(at:indexPath)
return currentLayout
private let radius = 200
private var decorator: UICollectionViewLayoutAttributes?
override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes
guard let decorator = self.decorator else
let layoutAttributes = UICollectionViewLayoutAttributes.init(forDecorationViewOfKind: elementKind, with: indexPath)
layoutAttributes.center = (self.collectionView?.center)!
layoutAttributes.bounds = CGRect.init(x: 0, y: 0, width: radius, height: radius)
self.decorator = layoutAttributes
return layoutAttributes
return decorator
lazy var s: UIDynamicItemBehavior =
let decorator = self.decorator!
let s = UIDynamicItemBehavior.init(items: [decorator])
s.angularResistance = 1
dynamicAnimator.addBehavior(s)
return s
()
func rotate(_ speed: CGFloat)
guard let decorator = self.decorator else return
s.addAngularVelocity(speed, for: decorator)
class TestCollectionViewController: UICollectionViewController
var startLocation = CGPoint.zero
var endLocation = CGPoint.zero
@objc private func gestureReader(_ gesture: UIPanGestureRecognizer)
let currentLocation = gesture.location(in: self.collectionView)
if gesture.state == .began
startLocation = currentLocation
else if gesture.state == .ended
endLocation = currentLocation
self.startRotatingView(start: startLocation, end: endLocation)
private func startRotatingView(start:CGPoint, end: CGPoint)
let dx = end.x - start.x
let dy = end.y - start.y
let distance = abs(sqrt(dx*dx + dy*dy))
if start.x < end.x
if start.y > end.y
//positive value of pi
self.circleAnimation(-distance)
else
//negitive value of pi
self.circleAnimation(distance)
else
if start.y > end.y
//positive value of pi
self.circleAnimation(-distance)
else
//negitive value of pi
self.circleAnimation(distance)
private func circleAnimation(_ angle:CGFloat)
(collectionView.collectionViewLayout as? TestCollectionViewLayout).map
$0.rotate(angle / 100)
// UIView.animate(withDuration: 0.7, delay: 0, options: .curveLinear, animations:
// self.collectionView.transform = CGAffineTransform.identity
// self.collectionView.transform = CGAffineTransform.init(rotationAngle: angle)
// ) (true) in
// //
//
override func viewDidAppear(_ animated: Bool)
super.viewDidAppear(animated)
// Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) (Timer) in
// self.rotate()
//
override func viewDidLoad()
super.viewDidLoad()
collectionView.collectionViewLayout = TestCollectionViewLayout()
collectionView.collectionViewLayout.register(UINib.init(nibName: "S1View", bundle: nil) , forDecorationViewOfKind: "background")
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.gestureReader(_:)))
panGesture.cancelsTouchesInView = false
self.collectionView.addGestureRecognizer(panGesture)
var data: [Int] = [1,2,3,4,5,6,7]
override func numberOfSections(in collectionView: UICollectionView) -> Int
return 1
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int
return data.count
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
return cell
【讨论】:
看起来不错,尝试后我会接受答案,非常感谢兄弟 你能帮我解决问题1,它是跟随手指旋转 太复杂了。 我将您的旋转功能与动画联系起来。起始定位有错误。 你能解释什么错误也许我们可以一起解决【参考方案2】:也许本教程会有所帮助:https://www.raywenderlich.com/1702-uicollectionview-custom-layout-tutorial-a-spinning-wheel
您的第一个问题是您正在旋转整个集合视图。想象一下,就像您将这些圆圈放在一张纸上,然后旋转那张纸。您不想旋转整个集合视图。您可能不想围绕一个点旋转圆圈,因为旋转会影响圆圈中的图像和文本。你只是想在圆周运动中改变圆圈的位置。
如果 UICollectionView 不起作用,您可以放弃它并使用常规 UIView 并将它们放置在圆形图案中(这些功能应该会有所帮助:https://gist.github.com/akhilcb/8d03f1f88f87e996aec24748bdf0ce78)。将视图布置成圆形后,您只需在用户拖动手指时更新每个视图的角度。将先前的角度存储在视图上,并在用户拖动手指时添加任何您想要的角度。一点点试错,应该不会太糟糕。
更新
使用集合视图的主要原因是如果您有很多项目并且需要像列表一样重用视图。如果您不需要重用视图,那么使用 UICollectionView 可能很难理解、自定义和更改内容。这是一个使用 UIPanGestureRecognizer 输入使用围绕圆圈旋转的常规视图的简单示例。
示例:
import UIKit
class ViewController: UIViewController
var rotatingViews = [RotatingView]()
let numberOfViews = 8
var circle = Circle(center: CGPoint(x: 200, y: 200), radius: 100)
var prevLocation = CGPoint.zero
override func viewDidLoad()
super.viewDidLoad()
for i in 0...numberOfViews
let angleBetweenViews = (2 * Double.pi) / Double(numberOfViews)
let viewOnCircle = RotatingView(circle: circle, angle: CGFloat(Double(i) * angleBetweenViews))
rotatingViews.append(viewOnCircle)
view.addSubview(viewOnCircle)
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(didPan(panGesture:)))
view.addGestureRecognizer(panGesture)
@objc func didPan(panGesture: UIPanGestureRecognizer)
switch panGesture.state
case .began:
prevLocation = panGesture.location(in: view)
case .changed, .ended:
let nextLocation = panGesture.location(in: view)
let angle = circle.angleBetween(firstPoint: prevLocation, secondPoint: nextLocation)
rotatingViews.forEach( $0.updatePosition(angle: angle))
prevLocation = nextLocation
default: break
struct Circle
let center: CGPoint
let radius: CGFloat
func pointOnCircle(angle: CGFloat) -> CGPoint
let x = center.x + radius * cos(angle)
let y = center.y + radius * sin(angle)
return CGPoint(x: x, y: y)
func angleBetween(firstPoint: CGPoint, secondPoint: CGPoint) -> CGFloat
let firstAngle = atan2(firstPoint.y - center.y, firstPoint.x - center.x)
let secondAnlge = atan2(secondPoint.y - center.y, secondPoint.x - center.x)
let angleDiff = (firstAngle - secondAnlge) * -1
return angleDiff
class RotatingView: UIView
var currentAngle: CGFloat
let circle: Circle
init(circle: Circle, angle: CGFloat)
self.currentAngle = angle
self.circle = circle
super.init(frame: CGRect(x: 0, y: 0, width: 60, height: 60))
center = circle.pointOnCircle(angle: currentAngle)
backgroundColor = .blue
required init?(coder aDecoder: NSCoder)
fatalError("init(coder:) has not been implemented")
func updatePosition(angle: CGFloat)
currentAngle += angle
center = circle.pointOnCircle(angle: currentAngle)
Circle 是一个结构,它只保存所有视图的中心、您希望它们相距多远(半径)以及用于计算上述 GitHub 链接中的角度的辅助函数。
RotatingViews 是围绕中间旋转的视图。
【讨论】:
你能用一些代码解释一下吗?我已经看过这些代码,但不确定如何在我的案例中使用这些代码,因为我不是一个很好的修改库,而且我对动画和类似的东西有点陌生。谢谢 我加了一个例子。以上是关于按照用户方向旋转collectionView的主要内容,如果未能解决你的问题,请参考以下文章