记一次失败的下拉刷新动画

最近想要写一个下拉刷新动画,但是因为一些原因失败了~现记录一下过程。这里主要记录的是思路,和那个没迈过去的坎儿。。。

最终大致效果如下:

预期效果图

一、结构

    页面很简单,一个自定义的顶部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也会依照关系继续执行物理环境提供的动画。

综上所述,如果物理动画过程提供物体的位置回调,并且可以重置物体位置,那么就可以解决这个问题,当然最好还是能使边界可以位移。

你可能感兴趣的:(记一次失败的下拉刷新动画)