美文网首页Swift工作笔记
Swift.轮转动画+Pop框架

Swift.轮转动画+Pop框架

作者: 王四猫 | 来源:发表于2018-09-20 15:16 被阅读43次
    效果图

    前言:

    项目改自Swift.轮转动画,100行代码搞定,页面布局没有变化,只是改变了动画效果,以及动画实现方式.所以有关布局的问题可以参考那篇文章.

    实现效果:

    长按中心View,使周围子View旋转,并将最下方的View放大,获取其image在中心View展示,当停止长按时动画停止.

    实现思路:

    所有的控件全部加到一个大的背景view上,本质上旋转的是这个背景view,在旋转背景view的同时,让它所有的子控件反向旋转,就实现了现在这种效果.

    使用pop框架添加旋转动画.给中心button一个.touchDown手势事件添加动画,再添加一个[.touchUpInside, .touchDragExit]手势事件来移除动画.

    最下方的view变大是通过CADisplayLink监听子view.layer.presention().bounds,来获取子View在当前页面的实际frame.x,当它处于一个范围,并且frame.y小于中心view.frame.y的时候.修改它的transform,来使其变大,并且修改它的tag来标记它已经属于变大状态,当它frame.x超出了预定范围,使其还原.

    遇到问题:

    1.动画实现方式选择问题:

    之前有尝试使用transform,CABasicAnimation,UIView.animation三种动画方式:都遇到了各种各样的问题:
    1.使用transform时很难实现长按按钮动画一直运行这个效果.
    2.使用CABasicAnimation时只修改view.layer.frame并不修改view.frame,所以会出现动画暂停时view闪回或者view展示位置与功能位置不符的问题.
    3.使用UIView.animation时很难流畅的控制它的暂停与开始.
    都无法完美实现,所以使用了faceBook提供的pop框架.pop框架在使用上与CABasicAnimation很像,建议可以尝试使用.

    2.使用pop动画,无法获取子view的即时frame问题:

    想使用KVO监听,但无法实现.
    选择使用CADisplayLink监听view.layer.presentation().layer.再通过

    let rect = subView.convert(bounds, to: UIApplication.shared.keyWindow)
    

    来获取子View在当前页面中的即时Frame.

    实现方式:

    1.添加背景透明view,中心圆圈view.

    2.添加周围旋转子view.

    3.添加旋转方法.

    4.修改最下方View的大小,并获取其image赋值给中心View


    1.添加背景透明view,中心圆圈view.

     /// 添加背景view,也是旋转的view
        private func setContentView() {
            setCircleView()
            contentView = UIView(frame: CGRect(x: 0, y: 0, width: ScreenInfo.Width, height: ScreenInfo.Width))
            contentView?.center = self.view.center
            self.view.addSubview(contentView!)
            contentView!.addSubview(circleView!)
        }
        /// 添加中间圆形view
        private func setCircleView(){
            let view = UIImageView(frame: CGRect(x: 0.5 * CGFloat(1 - PROPORTION) * ScreenInfo.Width + 10, y: 0.5 * CGFloat(1 - PROPORTION) * ScreenInfo.Width + 10, width: ScreenInfo.Width * CGFloat(PROPORTION) - 20, height: ScreenInfo.Width * CGFloat(PROPORTION) - 20))
            /// 为了适配保证size变化center不变
            let centerPoint = view.center
            view.frame.size = CGSize(width: ScreenInfo.Width * CGFloat(PROPORTION) - 40, height: ScreenInfo.Width * CGFloat(PROPORTION) - 40)
            view.center = centerPoint
            view.image = UIImage(named: "11")
            view.layer.cornerRadius = view.frame.width*0.5
            view.layer.masksToBounds = true
            view.isUserInteractionEnabled = true
            circleView = view
        }
    

    2.添加周围旋转子view.

       /// 布局旋转的子view
        private func rotationCircleCenter(contentOrgin: CGPoint,
                                          contentRadius: CGFloat,subnode: [String]){
            // 添加比例,实现当要添加的子view数量较多时候可以自适应大小.
            var scale: CGFloat = 1
            if subnode.count > 10 {
                scale = CGFloat(CGFloat(subnode.count) / 13.0)
            }
    
            for i in 0..<subnode.count {
                let x = contentRadius * CGFloat(sin(.pi * 2 / Double(subnode.count) * Double(i)))
                let y = contentRadius * CGFloat(cos(.pi * 2 / Double(subnode.count) * Double(i)))
                // 当子view数量大于10个,view.size变小,防止view偏移,要保证view.center不变.
                let view = EWSubView(frame: CGRect(x:contentRadius + 0.5 * CGFloat((1 + PROPORTION)) * x - 0.5 * CGFloat((1 - PROPORTION)) * contentRadius, y: contentRadius - 0.5 * CGFloat(1 + PROPORTION) * y - 0.5 * CGFloat(1 - PROPORTION) * contentRadius, width: CGFloat((1 - PROPORTION)) * contentRadius, height: CGFloat((1 - PROPORTION)) * contentRadius), imageName: subnode[i])
                let centerPoint = view.center
                view.frame.size = CGSize(width: CGFloat((1 - PROPORTION)) * contentRadius / scale , height: CGFloat((1 - PROPORTION)) * contentRadius / scale)
                view.center = centerPoint
                view.drawSubView()
                // 这个tag判断view是不是在最下方变大状态,非变大状态0,变大为1
                view.tag = 0
                // 获取子view在当前屏幕中的rect.来实现在最下方的那个变大
                let rect = view.convert(view.bounds, to: UIApplication.shared.keyWindow)
                let viewCenterX = rect.origin.x + (rect.width) / 2
                if viewCenterX > self.view.center.x - 20 && viewCenterX < self.view.center.x + 20 && rect.origin.y > (contentView?.center.y)! {
                    view.transform = view.transform.scaledBy(x: 1.5, y: 1.5)
                    view.tag = 1
                }
                contentView?.addSubview(view)
                viewArray.append(view)
            }
        }
    

    3.添加旋转方法.

       ///contentView点击中心自动旋转方法
        @objc func autoRotateContentView(){
            ///先添加整体背景旋转动画
            let animation: POPBasicAnimation = POPBasicAnimation(propertyNamed: kPOPLayerRotation)
            ///旋转角度,给一个大的数字优化体验,防止卡顿
            animation.toValue = CGFloat.pi * 16
            ///动画持续时间
            animation.duration = 32
            ///使动画重复
            animation.repeatCount = Int(MAXPRI)
            ///动画线性匀速展示
            animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
            ///将动画添加到contentView.layer
            self.contentView?.layer.pop_add(animation, forKey: "rotation")
    
            ///添加中心View反向旋转动画
            let subAnimation: POPBasicAnimation = POPBasicAnimation(propertyNamed: kPOPLayerRotation)
            ///将旋转角度反向
            subAnimation.toValue = -CGFloat.pi * 16
            ///给一个相同速度来实现中心view不动
            subAnimation.duration = 32
            subAnimation.repeatCount = Int(MAXPRI)
            subAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
            ///将动画添加到circleView.layer
            self.circleView?.layer.pop_add(subAnimation, forKey: "subRotation")
            ///监听子view.frame的监听器暂停
            displayLink.isPaused = false
            ///给周围旋转的子View添加同样的反向旋转动画
            for subView in viewArray {
                subView.layer.pop_add(subAnimation, forKey: "subRotation")
            }
        }
    

    4.修改最下方View的大小,并获取其image赋值给中心View

      /// 当subView在最下方时进行的修改其大小,修改中心circleView.image方法
        ///
        /// - Parameters:
        ///   - subView: subView
        ///   - bounds: 如果是长按动画进入的是subview.layer.presentation().bounds,手动滑动则是subView.bounds,需要根据bounds来                        计算subView在当前页面的frame
        func changeBottomSubView(subView: EWSubView, bounds: CGRect){
            ///获取view在当前页面的实际frame
            let rect = subView.convert(bounds, to: UIApplication.shared.keyWindow)
            let viewCenterX = rect.origin.x + (rect.width) / 2
            ///当View在页面中心线附近,并且处于下方时使用transform将其放大,并将中心imageView.image修改
            if viewCenterX > self.view.center.x - 20 && viewCenterX < self.view.center.x + 20 && rect.origin.y > (contentView?.center.y)! {
                if subView.tag == 0{
                    subView.transform = subView.transform.scaledBy(x: 1.5, y: 1.5)
                    ///使用tag标记确认其已被放大,防止重复放大,样式改变
                    subView.tag = 1
                    ///将放大的View提到最上方,保证展示效果
                    subView.bringSubview(toFront: subView)
                    self.circleView?.image = subView.imageView.image
                }
            }
            else {
                ///如果subView在变大状态
                if subView.tag == 1 {
                    ///将不在条件中的View恢复原样
                    subView.transform = subView.transform.scaledBy(x: 1/1.5, y: 1/1.5)
                    subView.tag = 0
                    contentView?.sendSubview(toBack: subView)
                }
            }
        }
    

    demo地址:# EWCircleView-pop

    通过touchMove获取手势,通过transform来实现类似效果的项目:Swift.轮转动画,100行代码搞定

    有问题欢迎探讨.

    相关文章

      网友评论

        本文标题:Swift.轮转动画+Pop框架

        本文链接:https://www.haomeiwen.com/subject/itcnnftx.html