美文网首页
跟随scrollView滑动且可缩放的headerView

跟随scrollView滑动且可缩放的headerView

作者: 大成小栈 | 来源:发表于2024-06-23 15:44 被阅读0次
    跟随scrollView滑动且可缩放的headerView

    你是不是想实现这样的效果?
    collectionView有背景图,且为headerView;headerView可跟随collectionView滚动;headerView在滚动过程中有缩放效果;collectionView滚动时,navBar渐变。

    这样的设计很常见,代码逻辑如下:

    import UIKit
    import Combine
    import SnapKit
    
    class DanceListController: UIViewController {
        
        typealias NavigationView = TemplateListController.NavigationView
        
        // 跟随scrollView滑动且可缩放的headerView
        private lazy var topView = TopView()
        
        // 自定义导航视图(初始化时为透明)
        private lazy var navigationView = NavigationView()
        
        // 滚动视图
        private lazy var collectionView = UICollectionView(
            frame: view.bounds,
            collectionViewLayout: createCompositionalLayout()
        )
        
        private(set) var viewModel: ViewModel
        
        var dataSource: UICollectionViewDiffableDataSource<Section, Item>!
        
        private var cancellables = Set<AnyCancellable>()
        
        var topImgHeight: CGFloat {
            (view.width * (44.0 + 375) / 375) * (560.0 / 420)
        }
        
        var topImgOffset: CGFloat {
            -(topImgHeight * (200.0 / 560))
        }
        
        var blankHeight: CGFloat {
            (topImgHeight + topImgOffset) * 0.6
        }
        
        init(id: String, type: Int, title: String? = nil, models: [Template] = []) {
            self.viewModel = ViewModel(cid: id, ctype: type, title: title, models: models)
            super.init(nibName: nil, bundle: nil)
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override var preferredStatusBarStyle: UIStatusBarStyle {
            if #available(iOS 17.0, *) {
                .default
            } else {
                .lightContent
            }
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            configureUI()
            configureDataSource()
            bindData()
        }
        
        override func viewWillAppear(_ animated: Bool) {
            super.viewWillAppear(animated)
            navigationController?.setNavigationBarHidden(true, animated: true)
        }
        
        override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            viewModel.loadPlayer(model: nil)
        }
        
        func configureUI() {
            
            navigationView.titleLabel.alpha = 0
            navigationView.titleLabel.text = viewModel.title
            navigationView.titleLabel.textColor = .white
            navigationView.backButton.setImage(UIImage(named: "ic_navigation_back")?.withTintColor(.white), for: .normal)
            
            view.backgroundColor = .hex(0x03141A)
            
            // 
            view.addSubview(topView)
            topView.width = view.width * (44 + 375) / 375
            topView.height = topView.width * 560 / 420
            topView.centerX = view.width / 2
            topView.top = topImgOffset
            
            // 
            view.addSubview(navigationView)
            navigationView.snp.makeConstraints {
                $0.leading.trailing.equalToSuperview()
                $0.top.equalToSuperview().offset(Const.safeAreaTop)
                $0.height.equalTo(44)
            }
            
            // 
            view.addSubview(collectionView)
            collectionView.snp.makeConstraints {
                $0.top.equalToSuperview().offset(44 + Const.safeAreaTop)
                $0.leading.trailing.bottom.equalToSuperview()
            }
            
            collectionView.showsVerticalScrollIndicator = false
            collectionView.showsHorizontalScrollIndicator = false
            collectionView.backgroundColor = .clear
            collectionView.delegate = self
            
            let refresh = UIRefreshControl()
            refresh.tintColor = .init(white: 1, alpha: 0.7)
            refresh.addAction(.init(handler: { [unowned self] _ in
                refreshNewDataAction()
            }), for: .valueChanged)
            collectionView.refreshControl = refresh
            
        }
        
        func scrollViewDidScroll(_ scrollView: UIScrollView) {
            let offsetY = scrollView.contentOffset.y
            topView.top = topImgOffset - offsetY
            
            let threshold = (topImgHeight + topImgOffset) / 2
            if offsetY > threshold {
                let ratio = (offsetY - threshold) / threshold
                navigationView.titleLabel.alpha = min(ratio, 1)
            } else {
                navigationView.titleLabel.alpha = 0
            }
        }
    
    // .............. controller中的其他代码,请自行添加!
    
    
    class TopView: UIView {
            
            private lazy var bgImageView = UIImageView().then {
                $0.image = UIImage(named: "pexels_top_image")
                $0.contentMode = .scaleAspectFill
                $0.backgroundColor = .clear
            }
            
            private(set) lazy var titleLabel = UILabel().then {
                $0.font = .poppins(24, weight: .bold)
                $0.textAlignment = .center
                $0.textColor = .white
            }
            
            private(set) lazy var tipsLabel = UILabel().then {
                $0.textColor = .white.withAlphaComponent(0.7)
                $0.font = .poppins(16, weight: .regular)
                $0.textAlignment = .center
            }
            
            lazy var gradientLayer: CAGradientLayer = .init().then {
                $0.colors = [
                    UIColor(hex: 0x03141A).withAlphaComponent(1).cgColor,
                    UIColor(hex: 0x000000).withAlphaComponent(0).cgColor
                ]
                $0.startPoint = CGPoint(x: 0.5, y: 1)
                $0.endPoint = CGPoint(x: 0.5, y: 0)
            }
            
            override init(frame: CGRect) {
                super.init(frame: frame)
                setupViews()
            }
            
            required init?(coder: NSCoder) {
                fatalError("init(coder:) has not been implemented")
            }
            
            func setupViews() {
                addSubview(bgImageView)
                bgImageView.snp.makeConstraints { make in
                    make.edges.equalToSuperview()
                }
                
                layer.addSublayer(gradientLayer)
                
                titleLabel.text = "Dance Like Them".localized
                addSubview(titleLabel)
                titleLabel.snp.makeConstraints { make in
                    make.centerX.equalToSuperview()
                    make.bottom.equalToSuperview().offset(-116)
                }
                
                tipsLabel.text = "Full body portrait to dancing video".localized
                addSubview(tipsLabel)
                tipsLabel.snp.makeConstraints { make in
                    make.centerX.equalToSuperview()
                    make.top.equalTo(titleLabel.snp.bottom).offset(2)
                }
            }
            
            override func layoutSubviews() {
                super.layoutSubviews()
                gradientLayer.frame = CGRect(x: 0, y: frame.size.height - 160, width: frame.size.width, height: 161)
            }
            
        }
    
    
    关于视图的设置:
    1. 各个视图的层级顺序:self.view >> topView >> navigationView >> collectionView >>;
    2. collectionView的起始位置,在navigationView以下;
    3. 其中 navigationView底色透明,title的alpha初始为0;
    4. topView的四边向屏幕外有延伸;
    navBar的渐变效果
    1. 其本身是透明的,但是topView底部有gradientLayer;
    2. title的透明度跟随滚动变化;
    监听滚动事件:
    // 设置topView随之滚动
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
            let offsetY = scrollView.contentOffset.y
            topView.top = topImgOffset - offsetY
            
            let threshold = (topImgHeight + topImgOffset) / 2
            if offsetY > threshold {
                let ratio = (offsetY - threshold) / threshold
                navigationView.titleLabel.alpha = min(ratio, 1)
            } else {
                navigationView.titleLabel.alpha = 0
            }
        }
    

    如果想设置滚动同时使topView有缩放效果,则计算一个scale并设置topView的transform即可。

    相关文章

      网友评论

          本文标题:跟随scrollView滑动且可缩放的headerView

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