译|如何使用 Flutter 创建动态岛和 ActivityKit

4,421 阅读3分钟

image.png

提示:机器翻译,仅供参考!
原文:medium.com/kbtg-life/h…

本教程将向您展示如何在 iOS 中设置动态岛。我使用的是 Xcode 14.1 Beta 2,但您可以将其用作 Native 和 Flutter 的指南。当 Xcode 14.1 发布或 Apple 对 Beta 版进行更改时,我将再次更新这篇文章。

让我们首先创建一个小部件工具包。转到File > Target

image.png

选择 iOS 平台并搜索 Widget Extension。

image.png

插入产品名称。您不需要选中“Include Configuration”框,因为它在本教程中没有任何作用,但我还是选中了它。

image.png

完成所有步骤后,您将获得一个小部件文件夹以及 Xcode 中的主文件夹。

image.png

现在让我们运行它。是的!现在我们的主页上有一个小部件 UI。只需触摸并按住主屏幕上的小部件套件,然后单击 + 即可将您的应用添加为小部件。

image.png

我在这里讨论 Widget Kit 是因为我们可以将它与 ActivityKit 一起使用。但是,我不会深入探讨,因为我们在本教程中的重点是动态岛(Dynamic Island)。

顶部只是关于 Widget Kit,那么动态岛上的 Live Activity 在哪里?让我们开始研究 ActivityKit 好吗?

首先,转到 info.plist 并添加一个新密钥“NSSupportsLiveActivities”并将其设置为 true。

image.png

添加 PizzaDeliveryAttributes.swift 文件,然后像这样实现 ActivityAttributes 协议。

import ActivityKit
import Foundation
struct PizzaDeliveryAttributes: ActivityAttributes {
    public typealias PizzaDeliveryStatus = ContentState
    public struct ContentState: Codable, Hashable {
       var driverName: String
       var deliveryTimer: ClosedRange<Date>
   }
    var numberOfPizzas: Int
    var totalAmount: String
    var orderNumber: String
}

把这个文件的目标添加到主项目和小部件的扩展中。你可以在Xcode的右边看到这个。

image.png

为动态岛创建一个新布局。我正在使用 Apple 开发者网站上的那个。

import SwiftUI
import WidgetKit
import ActivityKit

struct PizzaDeliveryActivityWidget: Widget {
    var body: some WidgetConfiguration {
        ActivityConfiguration(for: PizzaDeliveryAttributes.self) { context in
            LockScreenLiveActivityView(context: context)
            
        } dynamicIsland: { context in
            DynamicIsland {
                DynamicIslandExpandedRegion(.leading) {
                    Label("\(context.attributes.totalAmount) Pizzas", systemImage: "bag")
                        .foregroundColor(.indigo)
                        .font(.title2)
                }
                
                DynamicIslandExpandedRegion(.trailing) {
                    Label {
                        Text(timerInterval: context.state.deliveryTimer, countsDown: true)
                            .multilineTextAlignment(.trailing)
                            .frame(width: 50)
                            .monospacedDigit()
                    } icon: {
                        Image(systemName: "timer")
                            .foregroundColor(.indigo)
                    }
                    .font(.title2)
                }
                
                DynamicIslandExpandedRegion(.center) {
                    Text("\(context.state.driverName) is on their way!")
                        .lineLimit(1)
                        .font(.caption)
                }
                
                DynamicIslandExpandedRegion(.bottom) {
                    Button {
                        // Deep link into your app.
                    } label: {
                        Label("Call driver", systemImage: "phone")
                    }
                    .foregroundColor(.indigo)
                }
            } compactLeading: {
                Label {
                    Text("\(context.attributes.totalAmount) Pizzas")
                } icon: {
                    Image(systemName: "bag")
                        .foregroundColor(.indigo)
                }
                .font(.caption2)
            } compactTrailing: {
                Text(timerInterval: context.state.deliveryTimer, countsDown: true)
                    .multilineTextAlignment(.center)
                    .frame(width: 40)
                    .font(.caption2)
            } minimal: {
                VStack(alignment: .center) {
                    Image(systemName: "timer")
                    Text(timerInterval: context.state.deliveryTimer, countsDown: true)
                        .multilineTextAlignment(.center)
                        .monospacedDigit()
                        .font(.caption2)
                }
            }
            .keylineTint(.cyan)
        }
    }
}

struct LockScreenLiveActivityView: View {
    let context: ActivityViewContext<PizzaDeliveryAttributes>
    
    var body: some View {
        VStack {
            Spacer()
            Text("\(context.state.driverName) is on their way with your pizza!")
            Spacer()
            HStack {
                Spacer()
                Label {
                    Text("\(context.attributes.totalAmount) Pizzas")
                } icon: {
                    Image(systemName: "bag")
                        .foregroundColor(.indigo)
                }
                .font(.title2)
                Spacer()
                Label {
                    Text(timerInterval: context.state.deliveryTimer, countsDown: true)
                        .multilineTextAlignment(.center)
                        .frame(width: 50)
                        .monospacedDigit()
                } icon: {
                    Image(systemName: "timer")
                        .foregroundColor(.indigo)
                }
                .font(.title2)
                Spacer()
            }
            Spacer()
        }
        .activitySystemActionForegroundColor(.indigo)
        .activityBackgroundTint(.cyan)
    }
}

如果您有一个现有的 Widget Kit,则必须将 @main 从 Widget Kit 移动到一个新类,以便我们可以将两个当前的小部件与 ActivityKit 一起使用。

import SwiftUI
@main
struct PizzaDeliveryWidgets: WidgetBundle {
   var body: some Widget {
      widget()
      PizzaDeliveryActivityWidget()
   }
}

我的大部分教程来自 Apple 文档。也就是说,Apple 没有提供任何示例代码,所以我为自己创建了一个,以便在它发布时在我的项目中使用。您可以阅读 Apple 文档以获取更深入的详细信息。

Apple Developer Documentation:developer.apple.com/documentati… 以上是针对Native的。对于 Flutter 开发人员,您需要在 Dart 端实现更多功能以将请求发送到 Native。

如果想快速玩一下,你可以拉出这个 repo 来玩一下。 github.com/theamorn/fl…

就是这样!现在您可以享受 LiveActivity 和 动态岛。