美文网首页
跟随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