最近想要写一个下拉刷新动画,但是因为一些原因失败了~现记录一下过程。这里主要记录的是思路,和那个没迈过去的坎儿。。。
最终大致效果如下:
一、结构
页面很简单,一个自定义的顶部naviview,和一个tableview,所有动画都是在naviview上呈现, tableview主要提供偏移量。动画为定时动画+物理环境组合而成
二、实现
1.创建右侧按钮menu,menu由9个label组成(用label是方便之后赋值text,label统称为hoodle)
2.下拉tableview时,重绘naviview,让naviview底边,与偏移量关联,生成渐变弧线,并将bowstring的路径赋值给自己作为物理环境的边界
let color = self.heightAppreciation == 0 ? clearColor : UIColor.darkGray
color.set()
bowstring = UIBezierPath()
bowstring?.lineWidth = 1
bowstring?.lineCapStyle = .round
bowstring?.lineJoinStyle = .round
bowstring?.move(to: CGPoint(x: 0, y: 0))
bowstring?.addLine(to: CGPoint(x: self.width, y: 0))
bowstring?.addLine(to: CGPoint(x: self.width, y: naviHeight)) bowstring?.addQuadCurve(to: CGPoint(x: 0, y: naviHeight), controlPoint: CGPoint(x: self.width*1.0/2, y: naviHeight + self.heightAppreciation*1.8 + 5))
if self.refreshAnimationState == .end {
self.collision.removeBoundary(withIdentifier: "boundary" as NSCopying) self.collision.addBoundary(withIdentifier: "boundary" as NSCopying, for: bowstring!) self.animator.addBehavior(self.collision) }
3.下拉tableview,menu旋转,旋转角度同偏移量关联
4.当menu旋转达到某个阈值,menu的第4,5个hoodle 添加重力效果和物理碰撞,并在menu中心位置创建新的hoodle,并加入重力效果。这样小球就会自行下坠了。这里其实可以根据某个变量设置hoodle顺序依次掉落(不建议使用偏移量,变化区间太窄了)。并且可以为menu添加边界。
5.当下拉达到刷新阈值,naviview不再形变。此时松开手,为所有hoodle根据x轴排序,并在预先设置好的位置为相应的hoodle添加捕获,及hoodle变形的动画
private func hoodleChangeToLabel() {
self.collision.removeBoundary(withIdentifier:"boundary"asNSCopying)
for I in 0..
let hoodle =self.quiverMenu.rollHoodles[i]
hoodle.text=labels[i]
hoodle.backgroundColor=clearColor
hoodle.layer.borderWidth=1
hoodle.layer.borderColor = UIColor.red.cgColor
hoodle.textAlignment= .center
hoodle.font=UIFont.systemFont(ofSize:5)
let ballCenter =CGPoint(x:self.labelsFrames[i].origin.x+self.labelsFrames[i].size.width/2, y:self.labelsFrames[i].origin.y+self.labelsFrames[i].size.height/2)
//MARK: 添加物理环境后,变更frame会变形不完全,而transform会把当前数值作为tovalue,而设置的数值作为fromvalue
let snap =UISnapBehavior(item: hoodle, snapTo: ballCenter)
snap.damping=1
snap.dynamicAnimator?.delegate = self as UIDynamicAnimatorDelegate
self.animator.addBehavior(snap)
//使用animation动画的scale效果会出现闪现或无效问题(facebook 的pop框架 设置scale时 一样有问题)
let boundsanimation =CABasicAnimation(keyPath:"bounds")
boundsanimation.fromValue= hoodle.bounds
let cornerRadiusAnimation =CABasicAnimation(keyPath:"cornerRadius")
cornerRadiusAnimation.fromValue= hoodle.width/2
let group =CAAnimationGroup()
group.animations= [boundsanimation,cornerRadiusAnimation]
group.duration=0.7
//不设置tovalue,而是设置最终视图数值,来达到动画结束后视图变更为相应数值
hoodle.bounds=CGRect(x:0, y:0, width:labelWidth/2, height:labelWidth/2)
hoodle.layer.cornerRadius=labelWidth/4
hoodle.layer.add(group, forKey:nil)
if hoodle == self.quiverMenu.rollHoodles.last{
DispatchQueue.main.asyncAfter(deadline: .now()+0.7) {
self.loadingAnimation()
}
}
//物理环境会导致hoodle旋转,而吸附动画结束后,角度可能未归零
DispatchQueue.main.asyncAfter(deadline: .now()+1.8) {
UIView.animate(withDuration:0.3, animations: {
hoodle.transform=CGAffineTransform(rotationAngle:0)
})
}
}
}
6.所有hoodle归位,开始loading动画,因为没有找到物理环境下动画执行完成的回调,所以暂时用定时来实现,loading动画
//MARK:标题刷新动画
/*
定时执行hoodle放大缩小动画,顺序为rollHoodles数组循环序列
*/
private func loadingAnimation() {
self.refreshTimer.schedule(deadline: .now(), repeating:0.2)
self.refreshTimer.setEventHandler {
letflag =self.refreshFlag%self.labels.count
self.loadingAnimationToScaleHoodle(hoodle: self.quiverMenu.rollHoodles[flag] as! HoodleView)
self.refreshFlag+=1
}
self.refreshTimer.resume()
}
private func loadingAnimationToScaleHoodle(hoodle:HoodleView) {
letanimation =CAKeyframeAnimation(keyPath:"transform.scale")
let value1 =1
let value2 =2
let value3 =1
animation.duration=0.6
animation.values= [value1,value2,value3]
hoodle.layer.add(animation, forKey:"")
}
到此动画大致叙述完毕,下面开始话坑。。。
边界问题:因为物理边界添加的方式是,绘制新的曲线,删除旧的边界,然后添加新曲线路径给物理环境作为新的边界来刷新边界。下拉的时候没有问题,hoodle下坠超过新的边界前,新的边界就已经产生了。然而如果hoodle出现后,未触发捕获时,让tableview归位,hoodle,则会因为边界的刷新问题,穿越边界
试过了几种方式来弥补这个问题:
1.移动边界,没找到实现方式,暂无法移动o(╥﹏╥)o
2.删除旧边界之前,添加新的边界,会导致hoodle与边界重合,有可能将hoodle挤回界内,也可能挤出边界。同理在上划时,不删除旧的边界也会导致这个问题
3.给hoodle一个力,在上划时,使这个力方向向上,即先让hoodle移动再重绘边界。然而无法控制hoodle向上位移的距离
4.监听hoodle与边界碰撞,或监听hoodle底部坐标。当碰撞或底部坐标>=边界时,重置hoodle高度,然而,在执行物理动画的时候,变更hoodle的frame,或transform的dy,都只会让hoodle在预定位置闪现一下而已。即使此时先将hoodle从物理环境移除,hoodle也会依照关系继续执行物理环境提供的动画。
综上所述,如果物理动画过程提供物体的位置回调,并且可以重置物体位置,那么就可以解决这个问题,当然最好还是能使边界可以位移。