You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI实现滚动时导航栏元素动画(仿Apple TV应用效果)

在SwiftUI中实现Apple TV风格的导航栏滚动动画

需求说明

我想要实现类似Apple TV应用中的导航栏滚动动画,效果分为三个状态:

  • 初始状态:无标题,返回、添加、分享按钮为白色,导航栏不透明
  • 过渡状态:滚动到特定位置后,导航栏按钮颜色开始渐变切换
  • 最终状态:继续滚动后,导航栏显示标题,按钮颜色完全切换,同时导航栏变为半透明

目前我考虑在SwiftUI中借助UIScrollView的代理来实现,但不确定具体操作方式,希望了解如何完成以下动画:

  • 导航栏从不透明到半透明的平滑过渡
  • 各按钮的颜色渐变变化
  • 导航栏标题的渐显效果

实现方案

无需嵌套UIKit的UIScrollView,直接用SwiftUI原生组件结合滚动偏移监听,自定义导航栏即可实现所有效果。核心思路是通过滚动偏移量计算动画进度,驱动导航栏的各项属性变化。

1. 自定义导航栏组件

先实现一个可响应进度值的导航栏,进度值范围为0-1,对应从初始状态到最终状态的过渡:

import SwiftUI

struct CustomNavigationBar: View {
    // progress: 0 = 初始状态,1 = 最终状态
    let progress: CGFloat
    
    var body: some View {
        ZStack(alignment: .leading) {
            // 导航栏背景:从不透明黑到半透明黑
            Color.black
                .opacity(0.9 - 0.6 * progress)
            
            HStack {
                // 返回按钮:白色→深灰色渐变
                Button(action: {}) {
                    Image(systemName: "chevron.left")
                        .foregroundColor(Color(
                            red: 1 - 0.8 * progress,
                            green: 1 - 0.8 * progress,
                            blue: 1 - 0.8 * progress
                        ))
                }
                
                Spacer()
                
                // 标题:滚动到一定程度后渐显
                if progress > 0.3 {
                    Text("导航标题")
                        .font(.headline)
                        .foregroundColor(Color(
                            red: 1 - 0.8 * progress,
                            green: 1 - 0.8 * progress,
                            blue: 1 - 0.8 * progress
                        ))
                        .opacity(progress * 1.5 - 0.45)
                }
                
                Spacer()
                
                // 添加按钮:同返回按钮颜色渐变
                Button(action: {}) {
                    Image(systemName: "plus")
                        .foregroundColor(Color(
                            red: 1 - 0.8 * progress,
                            green: 1 - 0.8 * progress,
                            blue: 1 - 0.8 * progress
                        ))
                }
                
                // 分享按钮:同返回按钮颜色渐变
                Button(action: {}) {
                    Image(systemName: "square.and.arrow.up")
                        .foregroundColor(Color(
                            red: 1 - 0.8 * progress,
                            green: 1 - 0.8 * progress,
                            blue: 1 - 0.8 * progress
                        ))
                }
            }
            .padding(.horizontal, 20)
            .padding(.vertical, 12)
        }
        .frame(height: 56)
    }
}

2. 主视图与滚动监听

在主视图中监听ScrollView的滚动偏移量,计算动画进度并传递给自定义导航栏:

struct ContentView: View {
    @State private var scrollOffset: CGFloat = 0
    // 触发完全过渡的滚动阈值(可根据需求调整)
    private let animationThreshold: CGFloat = 100
    
    var body: some View {
        ZStack(alignment: .top) {
            ScrollView {
                VStack(spacing: 20) {
                    // 顶部占位视图,提供足够滚动空间触发动画
                    Color.clear.frame(height: 200)
                    
                    // 示例列表内容
                    ForEach(0..<20) { index in
                        Text("列表项 \(index)")
                            .frame(maxWidth: .infinity)
                            .padding()
                            .background(Color.gray.opacity(0.1))
                    }
                }
                .background(
                    // 通过GeometryReader监听滚动偏移
                    GeometryReader { proxy in
                        Color.clear
                            .preference(key: ScrollOffsetKey.self, value: proxy.frame(in: .named("scroll")).minY)
                    }
                )
                .onPreferenceChange(ScrollOffsetKey.self) { value in
                    // 转换为向下滚动的偏移量(取负值)
                    scrollOffset = -value
                }
            }
            .coordinateSpace(name: "scroll")
            
            // 传递进度值给导航栏,进度超过1时固定为1
            CustomNavigationBar(progress: min(scrollOffset / animationThreshold, 1))
        }
    }
}

// 自定义PreferenceKey用于传递滚动偏移量
struct ScrollOffsetKey: PreferenceKey {
    static var defaultValue: CGFloat = 0
    static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
        value = nextValue()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

关键细节说明

  1. 滚动偏移监听:通过GeometryReader和自定义PreferenceKey获取滚动偏移,纯SwiftUI实现,无需依赖UIKit代理。
  2. 进度控制:用scrollOffset / animationThreshold计算0-1的进度值,超过1时固定为1,避免动画过度。
  3. 平滑过渡:所有属性(背景透明度、按钮颜色、标题透明度)都通过进度值线性插值,SwiftUI会自动处理平滑动画。
  4. 标题显示时机:设置进度超过0.3时开始显示标题,通过opacity(progress * 1.5 - 0.45)实现渐显,避免突兀出现。

内容的提问来源于stack exchange,提问作者Shawn Frank

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 16:00:22