iOS - 基于Autolayout约束实现弹性动画,无需表视图/滚动视图可行吗?
只用两个UIView实现拖动弹性显示下方视图的方案
当然可以!完全不需要依赖UITableView或者UIScrollView,咱们用两个普通UIView加上手势识别和Core Animation就能搞定这个弹性拖动效果。下面是具体的实现思路和代码示例:
核心思路
- 把粉色视图作为底层容器,白色视图放在它上方(初始时白色视图完全覆盖粉色视图)
- 给白色视图添加
UIPanGestureRecognizer监听拖动手势 - 根据拖动偏移量实时调整白色视图的位置,同时处理松手后的弹性回弹动画
- 限制拖动边界,避免白色视图被拖得过低或过度向上暴露粉色视图
Swift 实现示例
import UIKit class ViewController: UIViewController { // 底层粉色视图 private let pinkView: UIView = { let view = UIView() view.backgroundColor = .systemPink view.translatesAutoresizingMaskIntoConstraints = false return view }() // 上层可拖动白色视图 private let whiteView: UIView = { let view = UIView() view.backgroundColor = .white view.translatesAutoresizingMaskIntoConstraints = false // 可选:添加顶部圆角模拟列表视觉效果 view.layer.cornerRadius = 16 view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] view.clipsToBounds = true // 可选:添加阴影增强层次感 view.layer.shadowColor = UIColor.black.cgColor view.layer.shadowOpacity = 0.1 view.layer.shadowOffset = CGSize(width: 0, height: -2) view.layer.shadowRadius = 8 return view }() // 记录拖动开始时白色视图的初始Y位置 private var initialViewY: CGFloat = 0 override func viewDidLoad() { super.viewDidLoad() setupViewHierarchy() setupPanGesture() } private func setupViewHierarchy() { view.addSubview(pinkView) view.addSubview(whiteView) NSLayoutConstraint.activate([ // 粉色视图铺满整个父视图 pinkView.topAnchor.constraint(equalTo: view.topAnchor), pinkView.leadingAnchor.constraint(equalTo: view.leadingAnchor), pinkView.trailingAnchor.constraint(equalTo: view.trailingAnchor), pinkView.bottomAnchor.constraint(equalTo: view.bottomAnchor), // 白色视图初始完全覆盖屏幕 whiteView.topAnchor.constraint(equalTo: view.topAnchor), whiteView.leadingAnchor.constraint(equalTo: view.leadingAnchor), whiteView.trailingAnchor.constraint(equalTo: view.trailingAnchor), whiteView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } private func setupPanGesture() { let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:))) whiteView.addGestureRecognizer(panGesture) } @objc private func handlePanGesture(_ gesture: UIPanGestureRecognizer) { let translation = gesture.translation(in: view) switch gesture.state { case .began: // 记录拖动起始位置 initialViewY = whiteView.frame.origin.y case .changed: // 计算新的顶部位置:初始位置 + 拖动Y偏移(向上拖动时translation.y为正) let newTopPosition = initialViewY + translation.y // 限制拖动范围:顶部最低为0(不露出屏幕外),最高不超过屏幕高度的1/3(可自定义) let clampedTop = max(0, min(newTopPosition, view.bounds.height / 3)) // 更新白色视图位置 whiteView.frame = CGRect(x: 0, y: clampedTop, width: view.bounds.width, height: view.bounds.height) case .ended, .cancelled: // 松手后触发弹性回弹动画,模拟系统滚动视图的弹性效果 UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: .curveEaseOut) { self.whiteView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height) } default: break } } }
关键细节说明
- 手势处理逻辑:通过
UIPanGestureRecognizer的translation(in:)获取实时拖动偏移,在.changed状态动态更新视图位置,同时做边界限制避免过度拖动。 - 弹性动画实现:松手后使用
UIView.animate的弹簧参数(usingSpringWithDamping和initialSpringVelocity),还原和UITableView一致的自然弹性回弹效果。 - 视觉优化:给白色视图添加圆角和阴影,能让整体效果更接近列表的视觉体验。
可选扩展
如果需要实现“拖动到一定距离后停留”的效果,可以在.ended状态判断总拖动偏移量,超过阈值就停留在目标位置,否则回弹:
// 在.ended/.cancelled分支替换原代码 let totalOffset = whiteView.frame.origin.y - initialViewY if totalOffset > view.bounds.height / 6 { // 停留到半露出位置 UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.6) { self.whiteView.frame = CGRect(x: 0, y: self.view.bounds.height / 3, width: self.view.bounds.width, height: self.view.bounds.height) } } else { // 回弹到初始位置 UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: .curveEaseOut) { self.whiteView.frame = CGRect(x: 0, y: 0, width: self.view.bounds.width, height: self.view.bounds.height) } }
内容的提问来源于stack exchange,提问作者Saif
相关产品推荐
相关产品推荐

