XZ_Swift之刷新的原理及实现、美团刷新

1、简单刷新
苹果默认的刷新控件存在的问题:
1>拉到一定距离自动刷新,即使用户没有松手也会刷新,给用户的体验不好,且浪费用户的流量;
2>调用 beginRefreshing,不显示菊花转圈;

刷新的实现思路:
1>、保证用户拉到足够距离之后,放手,才会刷新;
2>、继承自 UIControl,所有的接口定义和苹果原生基本一致,通过 KVO 监听刷新控件的 contentOffset

创建3个文件:
XZRefreshControl - 负责 刷新 相关的’逻辑’处理
XZRefreshView - 负责 刷新 相关的 UI 显示和动画
XZRefreshView.xib - 跟 XZRefreshView 进行关联,负责页面的搭建

在 XZRefreshControl 中
定义记录属性,记录父视图,父视图应该是scrollView或其子类

private weak var scrollView: UIScrollView?

weak的原因:因为父视图本身会对他强引用,addSubView 由父视图来调用,父视图对刷新控件进行强引用,如果刷新控件再对父视图进行强引用,就会造成循环引用

在 willMove toSuperview 方法中,使用 KVO 监听父视图的 contentOffset,并且计算刷新控件的高度,使用 addSubView 添加刷新控件的时候,会调用当前方法

override func willMove(toSuperview newSuperview: UIView?) {
    super.willMove(toSuperview: newSuperview)
    // 判断父视图的类型
    guard let sv = newSuperview as? UIScrollView else {
       return
    }
   // 记录父视图
    scrollView = sv
   // KVO监听父视图的 contentOffset
    scrollView?.addObserver(self, forKeyPath: "contentOffset", options: [], context: nil)
}

注意:使用完 KVO 必须释放,否则会引起程序的崩溃
在 removeFromSuperview 中移除监听
注意:要在调用 super.removeFromSuperview( ) 之前移除,因为调用 super.removeFromSuperview( ) 之后,super 就不存在了

override func removeFromSuperview() {
    // superView 还存在
    superview?.removeObserver(self, forKeyPath: "contentOffset”)
    super.removeFromSuperview()
    // superView 不存在
}

添加 KVO 的监听方法,在监听方法中监听父视图的 contentOffset;
判断临界点:

private let XZRefreshOffset: CGFloat = 60

判断用户是否拖动,如果拖动:是否超过临界点 & 是否是 Normal 状态,设置状态为 Pulling;如果没有超过临界点 & 状态是 Pulling,设置状态为 Normal;
如果用户没有拖动:判断状态是否是 Pulling,如果是 Pulling,调用开始刷新方法

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?,  context:UnsafeMutableRawPointer?) {
     guard let sv = scrollView else {
          return
     }
     let height = -(sv.contentInset.top + sv.contentOffset.y)
     if height < 0 {
        return
     }
     // 可以根据高度设置刷新控件的 frame
     self.frame = CGRect(x: 0,
                         y: -height,
                         width: sv.bounds.width,
                         height: height)
     // 判断临界点 - 只需要判断一次
     if sv.isDragging {
          if height > XZRefreshOffset && (refreshView.refreshState == .Normal) {
                refreshView.refreshState = .Pulling
          }else if height <= XZRefreshOffset && (refreshView.refreshState == .Pulling) {
                refreshView.refreshState = .Normal
          }
      }else {
          // 放手刷新 - 判断是否超过临界点
          if refreshView.refreshState == .Pulling {
                beginRefreshing()
                // 发送刷新数据事件
                sendActions(for: .valueChanged)
           }
       }
 }

创建枚举值,记录刷新的三种状态:普通状态、超过临界点,开始刷新、超过临界点,用户放手,开始刷新

/// 刷新状态
///
/// - Normal:      普通状态,什么都不做
/// - Pulling:     超过临界点,如果放手,开始刷新
/// - WillRefresh: 用户超过临界点,并且放手
enum XZRefreshState {
    case Normal
    case Pulling
    case WillRefresh
}

在 XZRefreshView 中使用刷新状态设置页面和动画

var refreshState: XZRefreshState = .Normal {
    didSet {
        switch refreshState {
        case .Normal:
              // 恢复状态
              imgIcon.isHidden = false
              indicator.stopAnimating()
              labelTip.text = "继续拖拽…"
              UIView.animate(withDuration: 0.25, animations: {                    
                  self.imgIcon.transform = CGAffineTransform.identity
              })
         case .Pulling:
              labelTip.text = "再向下拖拽…"
              UIView.animate(withDuration: 0.25, animations: {
                  self.imgIcon.transform = CGAffineTransform(rotationAngle: (.pi - 0.001))
              })
         case .WillRefresh:
              labelTip.text = "正在刷新中..."
              // 隐藏提示图标
              imgIcon.isHidden = true
              // 显示菊花
              indicator.startAnimating()
          }
       }
}

实现效果
这里写图片描述

2、下拉刷新带图片
创建 XZPictureRefreshView.xib 将需要展示的图片的底部跟xib的底部相同,在下拉刷新的时候就可以看到图片了

实现效果
这里写图片描述

3、模仿美团刷新
创建 XZMTRefreshView.xib 负责页面布局
创建 XZMTRefreshView.swift 负责页面的逻辑

最主要的点是:
1>根据父视图的高度,设置袋鼠的缩小/放大;
2>将图片生成 GIF 动画;
3>设置袋鼠始终保持在地球的底部的中心位置;

根据父视图高度修改袋鼠的大小

/// 父视图高度
    override var parentViewHeight: CGFloat {
        didSet {
            print("父视图高度 \(parentViewHeight)")

            if parentViewHeight < kangarooCenterYOffset {
                return
            }

            // 高度:   35  -> 126
            // scale: 0.2 -> 1
            // 高度差 / 最大高度差
            // 35 == 1 -> 0.2
            // 126 == 0 -> 1
            var scale: CGFloat
            if parentViewHeight > 126 {
                scale = 1
            }else {
                scale = 1 - (126 - parentViewHeight) / (126 - 35)
            }

            imgKangarooView.transform = CGAffineTransform(scaleX: scale, y: scale)
        }
    }

房子动画、袋鼠动画和袋鼠的位置的实现

override func awakeFromNib() {
        // 1.房子
        let bImage1 = #imageLiteral(resourceName: "icon_building_loading_1")
        let bImage2 = #imageLiteral(resourceName: "icon_building_loading_2")
        // 房子gif
        imgBuildingView.image = UIImage.animatedImage(with: [bImage1,bImage2], duration: 0.5)

        // 2.地球
        let anim = CABasicAnimation(keyPath: "transform.rotation")
        // 2 * Double.pi 顺时针转 -2 * Double.pi 逆时针转
        anim.toValue = -2 * Double.pi
        anim.repeatCount = MAXFLOAT
        anim.duration = 3
        anim.isRemovedOnCompletion = false

        imgEarthIconView.layer.add(anim, forKey: nil)

        // 3.袋鼠:先设置锚点,然后设置 frame 或 center
        // 0> 设置袋鼠动画
        let kImage1 = #imageLiteral(resourceName: "icon_small_kangaroo_loading_1")
        let kImage2 = #imageLiteral(resourceName: "icon_small_kangaroo_loading_2")
        imgKangarooView.image = UIImage.animatedImage(with: [kImage1, kImage2], duration: 0.5)

        // 1>设置锚点
        imgKangarooView.layer.anchorPoint = CGPoint(x: 0.5, y: 1.0)
        // 2>设置center
        let x = self.bounds.width * 0.5
        let y = self.bounds.height - kangarooCenterYOffset
        imgKangarooView.center = CGPoint(x: x, y: y)

        imgKangarooView.transform = CGAffineTransform(scaleX: 0.2, y: 0.2)
    }

实现效果
这里写图片描述

详细代码地址:
https://github.com/CoderXAndZ/XZRefresh

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值