• 你还在使用传统的菊花转圈实现loading数据吗?
  • 发布于 1天前
  • 11 热度
    0 评论
前言
最近在开发中遇到了一个问题,就是在数据加载时如何给用户一个更好的视觉反馈。传统的菊花转圈固然经典,但总感觉有些单调。直到我发现了 SwiftUI 的 Shimmer Loading Animation 效果,这种闪烁加载动画真的是太炫了!其实就是我们常说的"骨架屏"。

今天就来分享一下如何在 SwiftUI 中实现这种超赞的加载动画效果。

什么是 Shimmer Effect?
Shimmer Effect 中文叫做"闪烁效果",是一种模拟光线在表面闪烁的视觉效果。在 APP 中,我们通常用它来表示内容正在加载中,给用户一种"内容即将出现"的期待感。你肯定在各种知名 APP 中见过这种效果,比如 Facebook、Instagram 等,它们在加载内容时都会显示一个灰色的占位符,上面有一道光线从左到右扫过,就像闪闪发光一样。

核心原理
Shimmer 效果的核心原理其实很简单:
1.创建一个线性渐变色 (LinearGradient)
2.让渐变色的起点和终点不断改变
3.通过动画让光线从左到右移动

4.将这个效果应用到占位符上


实现代码
首先,我们来创建一个基础的 Shimmer 修饰符:
struct Shimmer: ViewModifier {
    @Stateprivatevar isInitialState = true
    
    func body(content: Content) -> some View {
        content
            .mask(
                LinearGradient(
                    gradient: Gradient(colors: [
                        .black.opacity(0.4),
                        .black,
                        .black.opacity(0.4)
                    ]),
                    startPoint: isInitialState ? 
                        UnitPoint(x: -0.3, y: -0.3) : 
                        UnitPoint(x: 1, y: 1),
                    endPoint: isInitialState ? 
                        UnitPoint(x: 0, y: 0) : 
                        UnitPoint(x: 1.3, y: 1.3)
                )
            )
            .animation(
                .linear(duration: 1.5)
                .delay(0.25)
                .repeatForever(autoreverses: false),
                value: isInitialState
            )
            .onAppear {
                isInitialState = false
            }
    }
}
创建一个简单的加载视图
现在我们创建一个简单的内容视图来展示效果:
struct ContentView: View {
    @Stateprivatevar isLoading = true
    
    var body: some View {
        VStack(spacing: 20) {
            if isLoading {
                // 加载状态
                VStack(spacing: 15) {
                    Rectangle()
                        .fill(Color.gray.opacity(0.3))
                        .frame(height: 200)
                        .cornerRadius(10)
                    
                    HStack {
                        Circle()
                            .fill(Color.gray.opacity(0.3))
                            .frame(width: 60, height: 60)
                        
                        VStack(alignment: .leading, spacing: 8) {
                            Rectangle()
                                .fill(Color.gray.opacity(0.3))
                                .frame(height: 16)
                                .cornerRadius(8)
                            
                            Rectangle()
                                .fill(Color.gray.opacity(0.3))
                                .frame(width: 120, height: 16)
                                .cornerRadius(8)
                        }
                        // 堆代码 duidaima.com
                        Spacer()
                    }
                }
                .modifier(Shimmer())
            } else {
                // 实际内容
                VStack(spacing: 15) {
                    Image("placeholder")
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(height: 200)
                        .cornerRadius(10)
                    
                    HStack {
                        Circle()
                            .fill(Color.blue)
                            .frame(width: 60, height: 60)
                        
                        VStack(alignment: .leading, spacing: 8) {
                            Text("美丽的风景")
                                .font(.headline)
                            
                            Text("瑞士 · 阿尔卑斯山")
                                .font(.subheadline)
                                .foregroundColor(.secondary)
                        }
                        
                        Spacer()
                    }
                }
            }
            
            Button(isLoading ? "加载中..." : "重新加载") {
                withAnimation {
                    isLoading.toggle()
                }
            }
            .disabled(isLoading)
        }
        .padding()
        .onAppear {
            // 模拟加载时间
            DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
                withAnimation {
                    isLoading = false
                }
            }
        }
    }
}
看看效果(其实是有动画的,只是我截图截断了):

更高级的用法
你还可以创建一个更加通用的 Shimmer 视图,这样以后用的时候就简单了:
struct ShimmerView: View {
    @Stateprivatevar startPoint = UnitPoint(x: -1.8, y: -1.2)
    @Stateprivatevar endPoint = UnitPoint(x: 0, y: -0.2)
    
    privatelet gradientColors = [
        Color.gray.opacity(0.2),
        Color.white.opacity(0.2),
        Color.gray.opacity(0.2)
    ]
    
    var body: some View {
        LinearGradient(
            colors: gradientColors,
            startPoint: startPoint,
            endPoint: endPoint
        )
        .onAppear {
            withAnimation(
                .easeInOut(duration: 1)
                .repeatForever(autoreverses: false)
            ) {
                startPoint = UnitPoint(x: 1, y: 1)
                endPoint = UnitPoint(x: 2.2, y: 2.2)
            }
        }
    }
}
实际应用场景
这种 Shimmer 效果特别适合用在:
1.列表加载 - 在 List 或 LazyVStack 中展示占位符
2.图片加载 - 在图片还未加载完成时显示
3.网络请求 - 在等待 API 响应时使用

4.分页加载 - 在加载更多内容时显示


注意事项
使用 Shimmer 效果时需要注意几点:
1.不要过度使用 - 太多的动画会让用户感到眼花缭乱
2.保持一致性 - 整个 APP 中的加载效果应该保持统一
3.性能考虑 - 在复杂页面中使用时要注意性能影响

4.无障碍支持 - 记得为使用辅助功能的用户提供替代方案


总结
Shimmer Loading Animation 真的是一个非常实用的 UI 效果,它能够显著提升用户体验,让等待变得不再那么枯燥。SwiftUI 的实现方式也相当简洁,几十行代码就能搞定。如果你想让自己的 APP 看起来更加专业和现代,不妨试试这个效果。相信用户会为你的用心而点赞的!
用户评论