【问题标题】:Slide out animation of three elements on top of each other三个元素相互叠加的滑出动画
【发布时间】:2021-09-16 21:49:03
【问题描述】:

我正在寻找制作以下动画的最佳方法。我尝试了一些解决方案,但似乎都没有解决我的问题,因为每个解决方案似乎都在某个时候失败了。我需要做的动画如下:

用户向上滑动手指,tableView & 1 & 2 向上滚动(就像 tableView & 1 & 2 是一个可滚动元素)。然后,当 2 在滚动时变得不可见时, 3 & 1 & tableView 变得可滚动(再次,好像它是一个可滚动元素)。然后,当 3 变得不可见(因为它被滚动)时,tableView 就是唯一的可滚动元素。

我尝试了什么:

  1. 我最初尝试使用简单的动画,例如根据滚动偏移量更改每个 1/2/3 元素的高度约束,虽然这对我来说似乎很好,但对审阅者来说并没有,因为他希望在隐藏之间更精确地滚动元素动画

  2. 然后我尝试将 panGesture 与滚动结合起来。我将 1 & 2 和 tableView 嵌入到一个滚动视图中,并使用委托函数 shouldRecognizeSimultaneouslyWith 在禁用 tableView 滚动时返回 true 为其设置 panGesture 识别器。然后,在与 3 相交时,我禁用了 panGesture 并尝试启用 tableView 滚动,但无法识别哪个 panGesture/scrolling 工作与否,哪个要禁用,哪个失败或单独/同时工作。

亲爱的开发人员您会如何处理它,以使动画流畅并符合描述?也许,你有一个很棒的主意:)

【问题讨论】:

    标签: ios swift animation uiview uianimation


    【解决方案1】:

    我认为你会在尝试切换滚动时打一场失败的战斗。

    这是另一种方法...

    • 将 tableView 添加为“容器”的子视图UIView
    • 将三个“顶部”视图添加为子视图
    • 给tableView一个contentInset.top三个view的高度加上垂直间距
    • 约束三个“顶部”视图,以便
      • 3 会粘在顶部,直到被1 推上去
      • 2 直到滑到 3 下方,1 将其向上推
      • 在 tableView 滚动时更新 1 的顶部约束

    您可以使用此示例代码进行尝试(无需@IBOutlet 连接):

    class ExampleViewController: UIViewController, UIScrollViewDelegate {
        
        let tableView: UITableView = {
            let v = UITableView()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.separatorInset = .zero
            return v
        }()
        let view1: UILabel = {
            let v = UILabel()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.textAlignment = .center
            v.backgroundColor = .systemRed
            v.text = "1"
            return v
        }()
        let view2: UILabel = {
            let v = UILabel()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.textAlignment = .center
            v.backgroundColor = .systemGreen
            v.text = "2"
            return v
        }()
        let view3: UILabel = {
            let v = UILabel()
            v.translatesAutoresizingMaskIntoConstraints = false
            v.textAlignment = .center
            v.backgroundColor = .systemBlue
            v.text = "3"
            return v
        }()
        
        // this will hold the tableView and the
        //  three other views
        let containerView: UIView = {
            let v = UIView()
            v.translatesAutoresizingMaskIntoConstraints = false
            // clip to bounds to prevent the "top" views from showing
            //  as they are "pushed up" out of bounds
            v.clipsToBounds = true
            return v
        }()
        
        // this constraint constant will be changed
        //  in scrollViewDidScroll
        @IBOutlet var view1TopConstraint: NSLayoutConstraint!
        
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // add our container view
            view.addSubview(containerView)
            
            // add our tableView and three "top" views
            containerView.addSubview(tableView)
            
            containerView.addSubview(view2)
            containerView.addSubview(view3)
            containerView.addSubview(view1)
            
            for v in [view1, view2, view3] {
                // all three "top" views should be
                //  equal width to tableView
                //  horizontally centered to tableView
                //  40-pts tall
                NSLayoutConstraint.activate([
                    v.widthAnchor.constraint(equalTo: tableView.widthAnchor),
                    v.centerXAnchor.constraint(equalTo: tableView.centerXAnchor),
                    v.heightAnchor.constraint(equalToConstant: 40.0),
                ])
            }
    
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
    
                // constrain container view with 20-pts "padding"
                containerView.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
                containerView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 20.0),
                containerView.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -20.0),
                containerView.bottomAnchor.constraint(equalTo: g.bottomAnchor, constant: -20.0),
    
                // constrain all 4 sides of tableView ot container view
                tableView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 0.0),
                tableView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 0.0),
                tableView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 0.0),
                tableView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 0.0),
    
            ])
            
            // view3 should stick to the top of the table,
            //  unless it's being pushed up by view1
            let v3Top = view3.topAnchor.constraint(equalTo: tableView.topAnchor)
            v3Top.priority = .defaultHigh + 1
    
            // view2 should stick to the bottom of view3,
            //  unless it's being pushed up by view1
            let v2Top = view2.topAnchor.constraint(equalTo: view3.bottomAnchor, constant: 4.0)
            v2Top.priority = .defaultHigh
    
            // view1 should ALWAYS be 4-pts from bottom of view2
            let v1TopA = view1.topAnchor.constraint(equalTo: view2.bottomAnchor, constant: 4.0)
            v1TopA.priority = .required
            
            // view1 should ALWAYS be greater-than-or-equal 4-pts from bottom of view3
            let v1TopB = view1.topAnchor.constraint(greaterThanOrEqualTo: view3.bottomAnchor, constant: 4.0)
            v1TopB.priority = .required
    
            // view1 top should ALWAYS be greater-than-or-equal top of tableView
            let v1TopC = view1.topAnchor.constraint(greaterThanOrEqualTo: tableView.topAnchor)
            v1TopC.priority = .required
            
            // 88-pts is 40-pts for view3 and view2 plus 4-pts vertical spacing
    
            // view1 top should NEVER be more-than 88-pts from top of tableView
            let v1TopD = view1.topAnchor.constraint(lessThanOrEqualTo: tableView.topAnchor, constant: 88.0)
            v1TopD.priority = .required
            
            // view1 top will start at 88-pts from top of tableView
            view1TopConstraint = view1.topAnchor.constraint(equalTo: tableView.topAnchor, constant: 88.0)
            view1TopConstraint.priority = .defaultHigh + 2
    
            // activate those constraints
            NSLayoutConstraint.activate([
                v3Top,
                v2Top,
                v1TopA,
                v1TopB,
                v1TopC,
                v1TopD,
                view1TopConstraint,
            ])
    
            tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
            tableView.dataSource = self
            tableView.delegate = self
    
            // top inset is
            //  three 40-pt tall views
            //  plus 4-pts vertical spacing between each
            //  and 4-pts vertical spacing below view1
            tableView.contentInset.top = 132
            tableView.contentOffset.y = -132
        }
        
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            // we're getting called when the tableView scrolls
            // invert the contentOffset y
            let y = -scrollView.contentOffset.y
            // subtract 44-pts (40-pt tall view plus 4-pts vertical spacing)
            view1TopConstraint.constant = y - 44.0
        }
        
    }
    
    extension ExampleViewController: UITableViewDelegate, UITableViewDataSource {
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 30
        }
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let c = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
            c.textLabel?.text = "Row \(indexPath.row)"
            return c
        }
    }
    

    下面是开始的样子:

    然后,在向上滚动一点之后(23 下方滑动):

    再滚动一点(13 向上推到视野之外):

    然后在1 保持在顶部时滚动表格:

    【讨论】:

    • 你就是那个男人!它绝对很棒,一定会使用它
    【解决方案2】:

    我正在使用您的想法的简化版本,其中包含 UIScrollView 和 3 个 UILabel 实例。

    您可以轻松地将其调整为 UITableView 和 3 个 UIView 实例。

    想法

    1. UIScrollView & 3 UILabel 实例有一个共同的超级视图。在这种情况下,它是UIViewController.view
    2. UIScrollView 布局为全屏(边到边)并延伸到这 3 个 UILabel 实例下方。
    3. UIScrollView 具有 contentInset.top = height_of_three_labels,因此它的内容从这些其他实例下方开始。
    4. 每当UIScrollView.contentOffset 发生变化时,我们都会为这三个实例移动frame.origin.y 以提供所需的效果。

    界面设置

    import UIKit
    
    class ViewController: UIViewController {
        
        let tileHeight: CGFloat = 60
        let view1 = UILabel()
        let view2 = UILabel()
        let view3 = UILabel()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let bounds = self.view.bounds
            
            let scrollView = UIScrollView(frame: bounds)
            scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            scrollView.delegate = self
            scrollView.alwaysBounceVertical = true
            self.view.addSubview(scrollView)
            
            view1.frame = CGRect(x: 0, y: 2*tileHeight, width: bounds.width, height: tileHeight)
            configureLabel(view1, text: "1", backgroundColor: .red)
            
            view2.frame = CGRect(x: 0, y: tileHeight, width: bounds.width, height: tileHeight)
            configureLabel(view2, text: "2", backgroundColor: .blue)
            
            view3.frame = CGRect(x: 0, y: 0, width: bounds.width, height: tileHeight)
            configureLabel(view3, text: "3", backgroundColor: .green)
            
            scrollView.contentInset = UIEdgeInsets(top: 3*tileHeight, left: 0, bottom: 0, right: 0)
            
            // 3 is between 2 and 1, 1 is at the top, order is important here
            self.view.addSubview(view2)
            self.view.addSubview(view3)
            self.view.addSubview(view1)
        }
        
        private func configureLabel(_ label: UILabel, text: String, backgroundColor: UIColor) {
            label.text = text
            label.textColor = .white
            label.font = .boldSystemFont(ofSize: 34)
            label.textAlignment = .center
            label.backgroundColor = backgroundColor
        }
        
    }
    

    滚动管理

    extension ViewController: UIScrollViewDelegate {
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            let offset = scrollView.adjustedContentInset.top + scrollView.contentOffset.y
            print(offset)
            if offset > 0 {
                view3.frame.origin.y = (offset > tileHeight) ? (tileHeight - offset) : 0
                view2.frame.origin.y = tileHeight - offset
                view1.frame.origin.y = 2*tileHeight - offset
            }
            else {
                view3.frame.origin.y = 0
                view2.frame.origin.y = tileHeight
                view1.frame.origin.y = 2*tileHeight
            }
        }
    }
    

    输出

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-29
      • 1970-01-01
      • 2021-10-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多