如何在计时器运行时用颜色填充矩形?

问题描述 投票:0回答:1

我创建了一个“通知横幅”,7.5 秒后,转到下一个通知。在右侧,我有一些项目符号,以便您可以在收到 5 条通知中的第 2 条通知时看到。

我想要实现的是,当计时器运行时,慢慢地将气泡从灰色填充到白色。因此,每个气泡都会慢慢填充,然后移动到下一个气泡,依此类推。目前正在像这样逐渐填充(请参见下面的视频)。

Video

struct SoleInAppNotifications: View {
    @State private var currentIndex: Int = 0
    @State private var isPaused: Bool = false
    @State var hasImage: Bool? = true

    let timer = Timer.publish(every: 7.5, on: .main, in: .common).autoconnect()
    var body: some View {
        HStack(spacing: .zero) {
            if hasImage ?? false {
                Image("raiff")
                    .padding(.trailing, 17)
            }
            VStack(alignment: .leading, spacing: .zero) {
                Text("Notification #1")
                    .foregroundColor(.white)
                    .lineLimit(1)
                    .padding(.bottom, 1)
                Text("Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, se")
                    .foregroundColor(.white)
                    .lineLimit(2)
            }
            VStack(spacing: .zero) {
                ForEach(0..<5, id: \.self) { index in
                    BubbleView(isActive: index == currentIndex, counter: currentIndex, countTo: 5)
                }
            }
        }
        .frame(maxWidth: .infinity)
        .padding(17)
        .background(Color.black)
        .onReceive(timer) { _ in
            if !isPaused, currentIndex < 5 {
                withAnimation {
                    currentIndex = (currentIndex + 1)
                }
            }
        }
    }
}

struct BubbleView: View {
    let isActive: Bool
    var counter: Int
    var countTo: Int

    var body: some View {
        ZStack {
            if isActive {
                RoundedRectangle(cornerRadius: 3)
                    .fill(Color.gray) 
                    .frame(width: 6, height: 24)
                    .overlay(
                        RoundedRectangle(cornerRadius: 3).trim(from:0, to: progress())
                            .foregroundColor(
                                (completed() ? Color.gray : Color.white)
                            )
                    ).animation(
                        .easeInOut(duration: 0.2)
                    )
            } else {
                Circle()
                    .fill(Color.gray) 
                    .frame(width: 6, height: 6)
                    .animation(.easeInOut)
                    .padding(4)
            }
        }
    }

    func completed() -> Bool {
        return progress() == 1
    }

    func progress() -> CGFloat {
        return (CGFloat(counter) / CGFloat(countTo))
    }
}

swift swiftui
1个回答
0
投票

要实现加载动画,进度应始终从 0 开始并以 1 结束。

第一期:

在进度函数中,您根据当前索引和计数设置进度。这导致最后一张幻灯片从 100% 开始,据我了解,这不是预期的结果。

第二期:

要在每个气泡上实现完整的动画,您需要根据静态值(您希望实现的速度)计算加载时间。例如。 7.5 秒(满分 100)(根据需要更改此值)。

我编辑了您的代码,如下所示。下面,我会建议一个更好的解决方案来实现动画。希望有帮助!

struct SoleInAppNotifications: View {
    //...
    @State private var progress: CGFloat = 0

    let timer = Timer.publish(every: 7.5/100,
                              on: .main,
                              in: .common).autoconnect()

    //...

    var body: some View {
        //...
        BubbleView(progress: $progress,
                   isActive: index == currentIndex,
                   counter: currentIndex,
                   countTo: 5)
        //...
        .onReceive(timer) { _ in
            withAnimation {
                if progress < 1 {
                    progress += 1/100
                } else if !isPaused, currentIndex < 5 {
                    currentIndex = (currentIndex + 1)
                    progress = 0
                }
            }
        }
    }
}

struct BubbleView: View {
    @Binding var progress: CGFloat

    //...

    /* Remove this function
    func progress() -> CGFloat {
        return (CGFloat(counter) / CGFloat(countTo))
    }*/
}

以下是我的做法:

(虽然是基本实现,但仍然可以进行一些改进)

class Bubble {
    var counter: CGFloat
    var countTo: CGFloat
    var i: CGFloat
    var isActive: Bool
    var progress: CGFloat
    
    init(counter: CGFloat,
         countTo: CGFloat,
         i: CGFloat,
         isActive: Bool,
         progress: CGFloat) {
        self.counter = counter
        self.countTo = countTo
        self.i = i
        self.isActive = isActive
        self.progress = progress
    }
}

struct SoleInAppNotifications: View {
    @StateObject private var vm = ViewModel()
    
    let animationTimer = Timer.publish(every: 7.5/100,
                                       on: .main,
                                       in: .common).autoconnect()
    
    var body: some View {
        HStack(spacing: .zero) {
            Image("raiff")
                .padding(.trailing, 17)
            
            VStack(alignment: .leading, spacing: .zero) {
                Text("Notification #1")
                    .foregroundColor(.white)
                    .lineLimit(1)
                    .padding(.bottom, 1)
                Text("Notification body")
                    .foregroundColor(.white)
                    .lineLimit(2)
            }
            
            VStack(spacing: .zero) {
                ForEach(vm.bubbles.indices, id: \.self) { i in
                    BubbleView(bubble: $vm.bubbles[i])
                        .onChange(of: vm.currentIndex) { _, _ in
                            vm.bubbles[i].isActive = CGFloat(i) == vm.currentIndex
                        }
                        .onChange(of: vm.progress) { _, _ in
                            print(vm.progress)
                            vm.bubbles[i].progress = vm.progress
                        }
                }
            }
        }
        .frame(maxWidth: .infinity)
        .padding(17)
        .background(Color.black)
        .onReceive(animationTimer){ _ in
            withAnimation {
                if vm.progress < 1 {
                    vm.progress += 1/100
                } else if vm.currentIndex < 5 {
                    withAnimation {
                        vm.currentIndex += 1
                        vm.progress = 0
                    }
                }
            }
        }
    }
}

extension SoleInAppNotifications {
    class ViewModel: ObservableObject {
        @Published var bubbles: [Bubble]
        @Published var currentIndex: CGFloat = 0
        @Published var progress: CGFloat = 0
        
        init() {
            self.bubbles = []
            for i in 0..<5 {
                bubbles.append(Bubble(counter: currentIndex,
                                      countTo: 5,
                                      i: CGFloat(i),
                                      isActive: i == 0 ? true : false,
                                      progress: progress))
            }
        }
    }
}

struct BubbleView: View {
    @Binding var bubble: Bubble
    
    var body: some View {
        if bubble.isActive {
            RoundedRectangle(cornerRadius: 3)
                .fill(Color.gray)
                .frame(width: 6, height: 24)
                .overlay(
                    RoundedRectangle(cornerRadius: 3)
                        .trim(from:0,
                              to: bubble.progress)
                        .foregroundColor(
                            (completed ? Color.gray : Color.white)
                        )
                ).animation(
                    .easeInOut(duration: 0.2)
                )
        } else {
            Circle()
                .fill(Color.gray)
                .frame(width: 6, height: 6)
                .animation(.easeInOut)
                .padding(4)
        }
    }
    
    var completed: Bool {
        bubble.progress == 1
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.