阅读视图

发现新文章,点击刷新页面。

iOS 26 仅需几行代码让 SwiftUI 7 液态玻璃界面焕发新春

在这里插入图片描述

概述

在今年的 WWDC 25 中,苹果为全平台推出了崭新的液态玻璃(Liquid Glass)皮肤。不仅如此,Apple 在此基础之上还打造了一整套超凡脱俗的动画和布局体系让 SwiftUI 7 界面焕发新机。

在这里插入图片描述

现在,我们只需寥寥几行代码就能将原本平淡无奇、乏善可陈的 SwiftUI 布局变成上面这般鲜活灵动。

在这里插入图片描述

想知道如何实现吗?看这篇就对啦!

在本篇博文中,您将学到如下内容:

  1. “一条大河窄又长”
  2. SwiftUI 7 全新玻璃特效来袭
  3. 融入,鲜活!

那还等什么呢?让我们马上迈入液态玻璃奇妙的特效世界吧? Let‘s go!!!;)


1. “一条大河窄又长”

在如今 App 现代化布局中,秃头小码农们非常喜爱像下面这般简洁、小巧、紧凑的全局菜单系统:

在这里插入图片描述

它就像一条长长的河流,伸缩自如,温柔又调皮的流入用户的心坎里。

不幸的是,目前它仿佛少了一点灵动的气息,而且感觉和 WWDC 25 中全新的液态玻璃也不太般配。

struct BadgesView: View {
    @Environment(ModelData.self) private var modelData
    @State private var isExpanded: Bool = false
    
    var body: some View {
        VStack(alignment: .center, spacing: Constants.badgeButtonTopSpacing) {
                if isExpanded {
                    VStack(spacing: Constants.badgeSpacing) {
                        ForEach(modelData.earnedBadges) {
                            BadgeLabel(badge: $0)
                        }
                    }
                }

                Button {
                    withAnimation {
                        isExpanded.toggle()
                    }
                } label: {
                    ToggleBadgesLabel(isExpanded: isExpanded)
                        .frame(width: Constants.badgeShowHideButtonWidth,
                               height: Constants.badgeShowHideButtonHeight)
                     
                }
                #if os(macOS)
                .tint(.clear)
                #endif
            }
            .frame(width: Constants.badgeFrameWidth)
    }
}

诚然,我们可以利用 SwiftUI 优秀的动画底蕴重新包装上面 BadgesView 视图的动画和转场效果,但这需要秃头小码农们宝贵的时间和头发,而且效果往往强差人意。

在这里插入图片描述

不过别担心,从 SwiftUI 7(iOS 26 / iPadOS 26 / macOS 26)开始,我们有了全新的选择,简单的不要不要的!

2. SwiftUI 7 全新玻璃特效来袭

从 WWDC 25 开始,全面支持 Liquid Glass 的 SwiftUI 7 推出了玻璃特效容器 GlassEffectContainer ,让我们真的可以对玻璃“为所欲为”:

在这里插入图片描述

GlassEffectContainer 能把多个带 glassEffect(_:in:) 的视图合并成一张“可变形的联合玻璃”,既省性能又能让玻璃形状彼此融合、 变形(morph)。

核心要点:

  • 用法:给子视图添加 .glassEffect(.liquid, in: container) 修改器,系统会把它们自动收集到同一个 GlassEffectContainer 中;
  • 效果:子视图的玻璃形状不再各自独立,而是当成一个整体渲染,可互相吸引、拼接、渐变和 morph;
  • 控制融合:通过容器的 spacing 值调节——值越大,子视图相距越远时就开始“粘”在一起;
  • 并发:@MainActor 隔离,线程安全。

总而言之,GlassEffectContainer 让多块“液态玻璃”合成一块可 morph 的超级玻璃,性能更高、动画更连贯。

在这里插入图片描述

同时,SwiftUI 7 还新增了两个配套方法 glassEffect(_:in:)glassEffectID(_:in:) : 在这里插入图片描述

在这里插入图片描述

我们可以利用它们结合 Namespace 来完成液态玻璃世界中的视图动画效果。

另外 SwiftUI 7 还专门为 Button 视图添加了 glass 按钮样式,真可谓超级“银杏化”:

在这里插入图片描述

有了这些 SwiftUI 中的宝贝,小伙伴们可以开始来打造我们的梦幻玻璃天堂啦!

3. 融入,鲜活!

将之前的 BadgesView 视图重装升级为如下实现:

struct BadgesView: View {
    @Environment(ModelData.self) private var modelData
    @State private var isExpanded: Bool = false
    @Namespace private var namespace
    
    var body: some View {
        GlassEffectContainer(spacing: Constants.badgeGlassSpacing) {
            VStack(alignment: .center, spacing: Constants.badgeButtonTopSpacing) {
                if isExpanded {
                    VStack(spacing: Constants.badgeSpacing) {
                        ForEach(modelData.earnedBadges) {
                            BadgeLabel(badge: $0)
                                .glassEffect(.regular, in: .rect(cornerRadius: Constants.badgeCornerRadius))
                                .glassEffectID($0.id, in: namespace)
                        }
                    }
                }

                Button {
                    withAnimation {
                        isExpanded.toggle()
                    }
                } label: {
                    
                    ToggleBadgesLabel(isExpanded: isExpanded)
                        .frame(width: Constants.badgeShowHideButtonWidth,
                               height: Constants.badgeShowHideButtonHeight)
                     
                }
                .buttonStyle(.glass)
                #if os(macOS)
                .tint(.clear)
                #endif
                .glassEffectID("togglebutton", in: namespace)
            }
            .frame(width: Constants.badgeFrameWidth)
        }
    }
}

上面这段新代码把“ earned 徽章列表”与底部的“展开/收起”按钮一起放进同一个 GlassEffectContainer 容器中,从而让它们全部参与 iOS 26 的「液态玻璃」合并渲染。

下面按“玻璃特性”逐句拆解:

  1. GlassEffectContainer(spacing: …)
  • 建立一块「联合玻璃画布」。
  • spacing 决定徽章彼此、徽章与按钮之间多早开始“粘”成一体:值越大,离得越远就开始融合。
  1. 展开时才出现的 VStack + ForEach
  • 每个 BadgeLabel 同时挂两个修饰符:
    • .glassEffect(.regular, in: .rect(cornerRadius: …))
      声明“我是 regular 风格玻璃,形状是圆角矩形”。
    • .glassEffectID(badge.id, in: namespace)
      给玻璃发身份证;同一 namespace 里身份证不同,SwiftUI 就能在增减徽章时做“液态 morph”——旧玻璃流走、新玻璃流进来,而不是生硬闪现。
  1. 底部 Button
  • .buttonStyle(.glass) 让按钮本身也是玻璃,但风格、圆角与徽章不同。
  • 同样用 .glassEffectID("togglebutton", in: namespace) 注册身份证,于是按钮的玻璃和上面徽章的玻璃被当成“同一张可变形大图”处理。
  • 展开/收起时,按钮玻璃会与刚出现(或消失)的徽章玻璃在边缘处“拉丝”融合,形成液态过渡。
  1. withAnimation { isExpanded.toggle() }
  • 状态变化被包进动画块,GlassEffectContainer 会同步驱动所有玻璃路径的 morph 动画:
    • 徽章从 0 高度“流”出来,边缘先与按钮玻璃粘连,再各自分离成独立圆角矩形。
    • 收起时反向流回,最终只剩按钮玻璃。
  1. 整体效果
    用户看到的不是“一行行控件出现”,而是一块完整的「可变玻璃」:
    • 展开 → 玻璃区域向下延伸,新徽章像水泡一样从主体里分裂长出;
    • 收起 → 多余部分被“吸”回按钮,边缘圆润地收缩消失。
      全程保持同一高光、折射、模糊背景,性能也优于多图层叠加。

在这里插入图片描述

简单来说,上面的实现用 GlassEffectContainer 把徽章与按钮收进同一块「液态玻璃」,凭借 glassEffectIDnamespace 让它们在展开/收起时像流体一样自然融合、morph,呈现出 iOS 26 独有的“整块玻璃可生长可收缩”的视觉魔法。

在这里插入图片描述

要注意哦,上面动图中按钮组背后的阴影是由于 gif 图片显示局限导致的,模拟器和真机实际测试的阴影效果可是美美哒的呢!

我们把 BadgesView 视图嵌入到主视图中,宝子们再来一起欣赏一下叹为观止的液态玻璃动画效果吧: 在这里插入图片描述

大功告成,打完收工,棒棒哒!💯

在这里插入图片描述

总结

在本篇文章中,我们讨论了在 iOS 26/iPadOS 26 里如何使用 SwiftUI 7 最新的液体玻璃系统来装饰小伙伴们的 App 界面。

在这里插入图片描述

感谢观赏,再会吧!8-)

Redux 中›ABC三个页面是如何通信的?

你的这种感觉非常正常!这也是很多初学者对 Redux 最大的误解。如果 A、B、C 三个页面分别有自己的 Store,那你就完全违背了 Redux 最核心的“单一数据源”原则,自然会陷入无法通讯的困境。

Redux 的核心理念是:整个应用有且只有一个全局 Store。A、B、C 三个页面共享这个唯一的 Store,而不是各自拥有一个。

让我用正确的 Redux 思维来为你重构这个问题,你会发现通讯变得非常简单和清晰。


正确的 Redux 结构:单一数据源

flowchart TD
    AppState["全局 AppState<br>包含三个页面的数据"]
    
    subgraph A [页面A]
        A_State[StateA]
        A_Action[ActionA]
    end

    subgraph B [页面B]
        B_State[StateB]
        B_Action[ActionB]
    end

    subgraph C [页面C]
        C_State[StateC]
        C_Action[ActionC]
    end

    AppState --> A_State
    AppState --> B_State
    AppState --> C_State

    A_Action -- dispatch --> Store
    B_Action -- dispatch --> Store
    C_Action -- dispatch --> Store

    Store -- 更新 --> AppState

实现步骤

第 1 步:定义全局的 State、Action 和 Reducer

State.swift - 单一数据源

// 整个应用只有一个根状态
struct AppState {
    // 页面A的状态,只是这个根状态的一个属性
    var pageAState: PageAState
    // 页面B的状态
    var pageBState: PageBState
    // 页面C的状态
    var pageCState: PageCState
    // 还可以有跨页面的共享状态
    var userIsLoggedIn: Bool
}

// 每个页面的状态仍然是独立的结构体,但被整合到AppState中
struct PageAState {
    var dataForA: String = ""
    var valueFromB: String? = nil // 用于接收来自B的数据
}

struct PageBState {
    var dataForB: Int = 0
    var valueFromC: String? = nil // 用于接收来自C的数据
}

struct PageCState {
    var dataForC: [String] = []
}

Action.swift - 统一的行为定义

// 所有页面的Action都集中在一个枚举中
enum AppAction {
    // 页面A的Action
    case pageA(PageAAction)
    case pageB(PageBAction)
    case pageC(PageCAction)
    // 全局的Action,如登录、登出
    case global(GlobalAction)
}

// 每个页面自己的Action枚举
enum PageAAction {
    case buttonTapped
    case dataLoaded(String)
    case receivedDataFromB(String) // 专门用于接收B的消息
}

enum PageBAction {
    case sliderValueChanged(Int)
    case sendDataToA(String) // 专门用于向A发送数据
}

enum PageCAction {
    case itemSelected(Int)
}

Reducer.swift - 统一的 reducer

// 根Reducer,负责组合所有页面的reducer
func appReducer(state: inout AppState, action: AppAction) -> Void {
    switch action {
    
    // 分解处理页面A的Action
    case .pageA(let pageAAction):
        pageAReducer(state: &state.pageAState, action: pageAAction)
        
    // 分解处理页面B的Action
    case .pageB(let pageBAction):
        pageBReducer(state: &state.pageBState, action: pageBAction)
        // B的Action可能会影响到其他页面!
        // 例如:当B发送数据时,需要更新A的状态
        if case .sendDataToA(let data) = pageBAction {
            state.pageAState.valueFromB = data // 直接修改A的状态
        }
        
    // 分解处理页面C的Action
    case .pageC(let pageCAction):
        pageCReducer(state: &state.pageCState, action: pageCAction)
        
    // 处理全局Action
    case .global(let globalAction):
        globalReducer(state: &state, action: globalAction)
    }
}

// 每个页面自己的reducer(纯函数)
func pageAReducer(state: inout PageAState, action: PageAAction) {
    switch action {
    case .buttonTapped:
        print("A的按钮被点击")
    case .dataLoaded(let data):
        state.dataForA = data
    case .receivedDataFromB(let dataFromB):
        state.valueFromB = dataFromB // 更新来自B的数据
    }
}

func pageBReducer(state: inout PageBState, action: PageBAction) {
    switch action {
    case .sliderValueChanged(let value):
        state.dataForB = value
    case .sendDataToA(let data):
        // 注意:这个Action的主要处理逻辑在根Reducer中
        // 这里可以处理B自身相关的状态更新
        print("B准备发送数据给A: \(data)")
    }
}

第 2 步:创建唯一的全局 Store

Store.swift

class Store: ObservableObject {
    @Published private(set) var state: AppState
    private let reducer: (inout AppState, AppAction) -> Void
    
    init(initialState: AppState, reducer: @escaping (inout AppState, AppAction) -> Void) {
        self.state = initialState
        self.reducer = reducer
    }
    
    func dispatch(_ action: AppAction) {
        reducer(&state, action)
    }
}

// 在应用入口创建唯一Store
let globalStore = Store(initialState: AppState(
    pageAState: PageAState(),
    pageBState: PageBState(), 
    pageCState: PageCState(),
    userIsLoggedIn: false
), reducer: appReducer)

第 3 步:在页面中使用全局 Store

PageAView.swift

struct PageAView: View {
    @EnvironmentObject var store: Store // 注入的是全局唯一的Store
    
    // 从全局State中取出页面A需要的部分状态
    private var pageAState: PageAState { store.state.pageAState }
    
    var body: some View {
        VStack {
            Text("页面A的数据: \(pageAState.dataForA)")
            // 显示从页面B传来的数据
            if let dataFromB = pageAState.valueFromB {
                Text("来自B的消息: \(dataFromB)")
            }
            Button("通知B") {
                // 派发Action,而不是直接调用B的方法
                store.dispatch(.pageB(.sendDataToA("你好,我是A!")))
            }
        }
    }
}

PageBView.swift

struct PageBView: View {
    @EnvironmentObject var store: Store // 同一个全局Store
    
    private var pageBState: PageBState { store.state.pageBState }
    
    var body: some View {
        VStack {
            Text("B的数值: \(pageBState.dataForB)")
            Button("发送数据到A") {
                // 通过全局Store派发Action
                store.dispatch(.pageB(.sendDataToA("Hello from B!")))
            }
            NavigationLink("去C") {
                PageCView()
            }
        }
    }
}

通信场景实现

现在,让我们看看如何实现具体的通信:

  1. A -> B 通信

    • A 中:store.dispatch(.pageB(.sendDataToA("你好,我是A!")))
    • 根Reducer 接收到 AppAction.pageB(.sendDataToA),它会: a. 调用 pageBReducer 处理 B 自身的状态(如果需要) b. 直接修改 state.pageAState.valueFromB
    • 由于 PageAView 依赖于 store.state.pageAState,SwiftUI 会自动重绘页面A,新的数据就显示出来了!
  2. B -> A 通信:(同上,方向相反)

  3. 任何页面 -> 全局状态

    • 任何页面都可以派发全局 Action:store.dispatch(.global(.loginSuccess))
    • 这会在根Reducer中处理,更新 state.userIsLoggedIn
    • 所有依赖 userIsLoggedIn 的页面都会自动更新!

总结

  • 只有一个 Store:这是 Redux 架构的绝对核心。
  • State 是组合的:每个页面的 State 是全局 AppState 的一个属性。
  • Action 是统一的:所有页面的 Action 都通过一个统一的枚举管理。
  • Reducer 是分形的:有一个根 Reducer,它负责将 Action 分发给各个页面的 Reducer 处理。
  • 通信方式:页面间通信就是派发一个目标为其他页面的 Action。这个 Action 会在根 Reducer 中被处理,并直接修改目标页面的 State

这种方式虽然初期需要更多样板代码,但带来的好处是巨大的:极其清晰的数据流、可预测的状态变化、易于调试和测试。所有页面间的耦合都被解除了,它们都只依赖于全局的 Store,而不是彼此。

Redux在iOS中的使用

好的,我们来详细探讨一下 Redux 在 iOS 开发中的应用。Redux 是一个源自 Web 前端(通常与 React 搭配)的架构模式,它因其单一数据源、状态不可变和纯函数Reducer 等特性,

一文精通-Flutter 状态管理

什么是状态管理? 在 Flutter 中,状态是指任何可以随时间变化的数据,这些数据的变化会影响用户界面的呈现。状态管理则是处理这些数据的变更、存储、传递以及在UI上反映这些变更的一套方法和架构模式。

调试 Swift 并发:我到底在哪个 Actor?

一、Swift 6 的“灵魂拷问”

写异步代码时你想知道:

“我现在是不是在主线程?”

于是老习惯:

print(Thread.isMainThread ? "主线程" : "后台")

Swift 6 直接报错:

'Thread.isMainThread' is unavailable from asynchronous contexts  
Work intended for the main actor should be marked with @MainActor

→ 别再关心线程,Swift 并发里正确问题是:

“我现在在哪个 Actor?”

二、为什么“线程”不够用了?

  • Swift 并发 = Actor 隔离模型
  • 同一 Actor 的任务可跑在不同线程(全局执行器调度)
  • 唯一安全保证 =“是否处于预期隔离域”,而非具体线程
旧概念/方法 Swift 6 新思维/方法
Thread.isMainThread MainActor.assertIsolated()
“跑在主线程” “跑在 MainActor”
“手动切线程” “让编译器/运行时调度”

三、调试神器:MainActor.assertIsolated()

func updateUI() {
    MainActor.assertIsolated("UI 必须在 MainActor 上更新")
    title = "Loaded"
}

行为:

构建配置 结果
Debug(Xcode 默认) 如果不在 MainActor → 立即 trap(可看到堆栈)
Release 无代码,零成本

陷阱演示

Task { // ⛔️ 后台 actor
    updateUI()   // Debug 下立刻崩溃
}

崩溃信息示例:

Task 1 Queue: com.apple.root.user-initiated-qos.cooperative (concurrent)

→ 队列名带 user-initiated + 无 main-thread → 确凿后台环境。

四、想“硬崩溃”用 preconditionIsolated()

MainActor.preconditionIsolated("生产环境也必须主线程")
  • Debug & Release 都会 crash
  • 用于“一旦跑错隔离域就是逻辑错误”的场景(如 UI 刷新)

五、自定义 Global Actor 也能断言

@globalActor
actor ImageCacheActor {
    static let shared = ImageCacheActor()
}

@ImageCacheActor
func mutateCache() {
    ImageCacheActor.assertIsolated("必须在我自己的隔离域")
    // 安全操作缓存
}

→ 与 MainActor 用法完全一致,调试信息同样显示队列名:

Queue: com.apple.root.default-qos.cooperative (concurrent)

六、快速判断“我不在 MainActor”

Swift 没有 assertNotMainActor,但可以反向利用:

#if DEBUG
// 临时检查:如果这里不崩溃,说明**在** MainActor → 证明我们跑错地方
MainActor.assertIsolated("应该 NOT 在主线程执行")
#endif

调试点技巧:

  1. 在断点处看 Debug Navigator → Queue

  2. 队列名含 main-thread → MainActor

    否则 → 后台隔离域

七、日志可视化:把隔离域打印出来

#if DEBUG
func logIsolation(_ tag: String = "") {
    let queueLabel = DispatchQueue.getSpecific(key: DispatchSpecificKey()) ?? "unknown"
    print("[\(tag)] Queue: \(queueLabel)  Thread: \(Thread.current)")
}
#endif

→ 结合 assertIsolated 可一次性确认“队列 + 线程 + 崩溃行”。

八、常见疑问速答

疑问 解答
“我用 Task { @MainActor in }就够了吧?” 那是提交任务时指定,调试时仍需确认内部是否确实在主线程
Thread.isMainThread真的不能用了吗?” Swift 6 语言模式下编译错误;用 MainActor.assertIsolated()替代
“断言会影响性能吗?” Release 下 assertIsolated无代码;preconditionIsolated才会留

九、一句话总结

“Swift 6 里,别再问‘我在哪个线程’,而应问‘我在哪个 Actor’。”

记住调试三步曲:

  1. 开发期:MainActor.assertIsolated("描述") → 早崩溃早修复
  2. 调试器:看 Queue 名 → main-thread 即安全
  3. 生产期:如逻辑错误必崩 → 换 preconditionIsolated

把“线程思维”换成“Actor 思维”,让编译器 + 运行时替你守好并发安全的大门!

苹果卡审情况将逐步缓解,合规的开发者请耐心等待~

背景

最近陆陆续续的有粉丝来询问关于AppStore审核时间久的问题。上周五也经历了一次历时30个小时的正在审核中

主要集中在长期卡在正在审核中!

简单来说:

苹果每次发布会来临之际,都会对AppStore审核上强度。毕竟在这种重大事件上,苹果公关部也不希望因为违规的产品,对新品发布造成影响。

最近审核情况

因为最近也有产品在迭代,所以直接上截图,让正在审核的同行们不必担心!

迭代第一次

审核时间约等于1.5小时

2.13.1.png

迭代第二次

审核时间约等于30小时

2.13.2.png

今日迭代

审核时间约等于0.5小时

2.13.3.png

额外说明

如果长期时间没有结果,现在正在审核中,可考虑直接撤销重新提交。因为大概率苹果把人力放在筹备发布会

在此关键时间建议不要过分的ASO操作与开放违规功能,切记不可顶风作案

遵守规则,方得长治久安,最后祝大家大吉大利,今晚过审!

相关推荐

# 苹果开发者续费大坑及成功续费方案!亲测有效

# Pingpong和连连的平替,让AppStore收款无需新增持有人。

# 期待iOS开发者加入,共同抵制“苹果税”反垄断招募令!

# 苹果加急审核是“绿色通道”还是“死亡陷阱”?

# 苹果开发者邮箱,突然收到11.2通知严重么?

# 不想被苹果卡审最好错开这两个提审时间

# 手撕苹果审核4.3是代码问题还是设计问题?

# 有幸和Appstore审核人员进行了一场视频会议特此记录。

知识星球

更多Appstore咨询问题,请关注知识星球。「提供1v1上架指导,帮助开发者解决Appstore的疑难杂症,助力每一位开发者!」

Flutter UI Components:闲来无事,设计整理了这几年来使用的UI组件库

本文详细介绍了一个完整的 Flutter UI 组件库的设计思路、架构实现和核心特性,包含 50+ 个高质量组件,支持主题切换、响应式设计等企业级功能。

📋 目录

🎯 项目概述

Flutter UI Components 是一个基于 Flutter 框架开发的企业级 UI 组件库,旨在为开发者提供一套完整、可定制、高性能的 UI 组件解决方案。

核心特性

  • 🎨 完整的主题系统:支持浅色/深色主题切换,自动适配系统主题
  • 📱 响应式设计:适配不同屏幕尺寸,支持平板和桌面端
  • 🔧 高度可定制:丰富的配置选项,满足各种设计需求
  • 性能优化:使用 const 构造函数,减少不必要的重建
  • 🧪 完整测试:单元测试和 Widget 测试覆盖
  • 🏗️ 模块化架构:清晰的目录结构,便于维护和扩展

组件统计

分类 组件数量 主要组件
按钮组件 5+ UIButton、UIGradientButton、UIHighlightButton
卡片组件 3+ UIDefaultCard、UICollectionView
导航组件 2+ UIAppBarDecorator、UISegmentedControl
反馈组件 8+ UIToolTip、UIToast、UIDialog、UISnackBar
数据展示 6+ UITagView、UIBadgeView、UIProgressIndicator
输入组件 4+ UITextField、UICheckBox、UIDropDownButton
布局组件 5+ UIListView、UIGridView、UITableView

🏗️ 系统架构设计

整体架构图

graph TB
    A[Flutter UI Components] --> B[Core Module 核心模块]
    A --> C[Components Module 组件模块]

    B --> D[Base Classes 基础类]
    B --> E[Theme System 主题系统]
    B --> F[Constants 常量定义]
    B --> G[Extensions 扩展方法]

    C --> H[Buttons 按钮组件]
    C --> I[Cards 卡片组件]
    C --> J[Navigation 导航组件]
    C --> K[Feedback 反馈组件]
    C --> L[Data Display 数据展示]
    C --> M[Input 输入组件]
    C --> N[Layout 布局组件]

    D --> O[BaseWidget]
    D --> P[BaseStatefulWidget]
    D --> Q[UIBasePage]

    E --> R[AppTheme]
    E --> S[ThemeConfig]
    E --> T[ThemeBuilder]

模块依赖关系

graph LR
    A[Components] --> B[Core]
    B --> C[Flutter Framework]

    D[BaseWidget] --> E[Theme System]
    D --> F[Constants]

    G[UI Components] --> D
    G --> H[Extensions]

    I[Example App] --> A
    I --> J[GetX State Management]

🔧 核心模块详解

1. 基础类设计

所有组件都继承自 BaseWidget,确保统一的接口和生命周期管理:

/// 基础Widget抽象类
/// 所有UI组件都应该继承此类,确保统一的接口和生命周期管理
abstract class BaseWidget extends StatelessWidget {
  const BaseWidget({super.key});

  /// 组件名称,用于调试和日志
  String get widgetName;

  /// 组件版本,用于版本管理
  String get version => '1.0.0';

  /// 是否启用调试模式
  bool get enableDebug => false;

  @override
  Widget build(BuildContext context) {
    if (enableDebug) {
      debugPrint('Building $widgetName v$version');
    }
    return buildWidget(context);
  }

  /// 子类需要实现的构建方法
  Widget buildWidget(BuildContext context);

  /// 获取组件的主题数据
  ThemeData getTheme(BuildContext context) {
    return Theme.of(context);
  }

  /// 获取组件的主题配置
  AppThemeConfig getThemeConfig(BuildContext context) {
    final theme = getTheme(context);
    return AppThemeConfig.fromBrightness(theme.brightness);
  }
}

2. 主题系统架构

主题系统采用接口抽象和配置分离的设计模式:

/// 主题配置接口
abstract class IThemeConfig {
  Color get primary;
  Color get secondary;
  Color get success;
  Color get error;
  Color get text;
  Color get background;
  Color get surface;
  // ... 更多颜色定义
}

/// 应用主题管理类
class AppTheme {
  static AppTheme? _instance;
  static AppTheme get instance => _instance ??= AppTheme._();

  /// 当前主题模式
  ThemeMode _themeMode = ThemeMode.system;

  /// 当前主题配置
  IThemeConfig? _customConfig;

  /// 主题配置构建器
  IThemeConfig Function(Brightness brightness)? _configBuilder;

  /// 获取当前主题配置
  IThemeConfig get currentConfig {
    if (_customConfig != null) {
      return _customConfig!;
    }

    if (_configBuilder != null) {
      final brightness = _themeMode == ThemeMode.dark
          ? Brightness.dark : Brightness.light;
      return _configBuilder!(brightness);
    }

    // 返回默认配置
    return _themeMode == ThemeMode.dark
        ? const DefaultDarkThemeConfig()
        : const DefaultThemeConfig();
  }

  /// 切换主题模式
  void switchTheme(ThemeMode mode) {
    if (_themeMode != mode) {
      _themeMode = mode;
      _notifyThemeListeners();
    }
  }
}

3. 常量系统设计

统一的常量管理确保设计一致性:

/// 应用常量定义
class AppConstants {
  // 间距常量
  static const double spacingXs = 4.0;
  static const double spacingSm = 8.0;
  static const double spacingMd = 16.0;
  static const double spacingLg = 24.0;
  static const double spacingXl = 32.0;

  // 圆角常量
  static const double radiusXs = 4.0;
  static const double radiusSm = 8.0;
  static const double radiusMd = 12.0;
  static const double radiusLg = 16.0;

  // 字体大小常量
  static const double fontSizeXs = 12.0;
  static const double fontSizeSm = 14.0;
  static const double fontSizeMd = 16.0;
  static const double fontSizeLg = 18.0;
  static const double fontSizeXl = 20.0;

  // 动画时长常量
  static const int animationDurationFast = 200;
  static const int animationDurationMd = 300;
  static const int animationDurationSlow = 500;
}

🎨 组件分类与实现

1. 按钮组件

按钮组件支持多种类型、尺寸和状态:

/// 按钮类型枚举
enum UIButtonType {
  primary,    // 主要按钮
  secondary,  // 次要按钮
  success,    // 成功按钮
  warning,    // 警告按钮
  error,      // 错误按钮
  info,       // 信息按钮
  outline,    // 轮廓按钮
  text,       // 文本按钮
}

/// 通用按钮组件
class UIButton extends BaseWidget {
  const UIButton({
    super.key,
    required this.text,
    this.onPressed,
    this.type = UIButtonType.primary,
    this.size = UIButtonSize.medium,
    this.isLoading = false,
    this.isDisabled = false,
    this.icon,
    this.fullWidth = false,
  });

  final String text;
  final VoidCallback? onPressed;
  final UIButtonType type;
  final UIButtonSize size;
  final bool isLoading;
  final bool isDisabled;
  final IconData? icon;
  final bool fullWidth;

  @override
  String get widgetName => 'UIButton';

  @override
  Widget buildWidget(BuildContext context) {
    final themeConfig = getThemeConfig(context);
    final isEnabled = onPressed != null && !isDisabled && !isLoading;

    return SizedBox(
      width: fullWidth ? double.infinity : null,
      child: ElevatedButton(
        onPressed: isEnabled ? onPressed : null,
        style: _buildButtonStyle(themeConfig),
        child: _buildButtonContent(themeConfig),
      ),
    );
  }

  ButtonStyle _buildButtonStyle(AppThemeConfig config) {
    return ElevatedButton.styleFrom(
      backgroundColor: _getBackgroundColor(config),
      foregroundColor: _getForegroundColor(config),
      padding: _getPadding(),
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(AppConstants.radiusSm),
      ),
      elevation: type == UIButtonType.outline ? 0 : 2,
    );
  }
}

2. 工具提示组件

工具提示组件支持多方向显示和丰富的自定义选项:

/// 工具提示方向枚举
enum UIToolTipDirection {
  up, down, left, right,
  upLeft, upRight, downLeft, downRight,
}

/// 工具提示配置
class UIToolTipConfig {
  const UIToolTipConfig({
    this.direction = UIToolTipDirection.down,
    this.distance = 8.0,
    this.arrowSize = 8.0,
    this.backgroundColor = Colors.black87,
    this.textColor = Colors.white,
    this.borderRadius = 8.0,
    this.padding = const EdgeInsets.all(12.0),
    this.showArrow = true,
    this.animationDuration = const Duration(milliseconds: 200),
  });

  final UIToolTipDirection direction;
  final double distance;
  final double arrowSize;
  final Color backgroundColor;
  final Color textColor;
  final double borderRadius;
  final EdgeInsets padding;
  final bool showArrow;
  final Duration animationDuration;
}

/// 主要的工具提示组件
class UIToolTip extends StatefulWidget {
  const UIToolTip({
    super.key,
    required this.content,
    this.config = const UIToolTipConfig(),
    this.controller,
    required this.child,
  });

  final Widget content;
  final UIToolTipConfig config;
  final UIToolTipController? controller;
  final Widget child;

  @override
  State<UIToolTip> createState() => _UIToolTipState();
}

3. 渐变色进度指示器

自定义绘制的渐变色圆形进度指示器:

/// 渐变色圆形进度指示器组件
class UIGradientCircularProgressIndicator extends StatelessWidget {
  const UIGradientCircularProgressIndicator({
    super.key,
    required this.radius,
    this.strokeWidth = 4.0,
    this.colors,
    this.stops,
    this.strokeCapRound = false,
    this.backgroundColor = const Color(0xFFEEEEEE),
    this.totalAngle = 2 * pi,
    this.value,
    this.animationDuration = const Duration(milliseconds: 300),
    this.child,
  });

  final double radius;
  final double strokeWidth;
  final List<Color>? colors;
  final List<double>? stops;
  final bool strokeCapRound;
  final Color backgroundColor;
  final double totalAngle;
  final double? value;
  final Duration animationDuration;
  final Widget? child;

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: Size(radius * 2, radius * 2),
      painter: _GradientCircularProgressPainter(
        strokeWidth: strokeWidth,
        colors: colors ?? [Theme.of(context).primaryColor],
        stops: stops,
        strokeCapRound: strokeCapRound,
        backgroundColor: backgroundColor,
        totalAngle: totalAngle,
        value: value,
      ),
      child: child,
    );
  }
}

class _GradientCircularProgressPainter extends CustomPainter {
  _GradientCircularProgressPainter({
    required this.strokeWidth,
    required this.colors,
    this.stops,
    required this.strokeCapRound,
    required this.backgroundColor,
    required this.totalAngle,
    this.value,
  });

  final double strokeWidth;
  final List<Color> colors;
  final List<double>? stops;
  final bool strokeCapRound;
  final Color backgroundColor;
  final double totalAngle;
  final double? value;

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final radius = (size.width - strokeWidth) / 2;

    // 绘制背景圆环
    final backgroundPaint = Paint()
      ..color = backgroundColor
      ..strokeWidth = strokeWidth
      ..style = PaintingStyle.stroke
      ..strokeCap = strokeCapRound ? StrokeCap.round : StrokeCap.butt;

    canvas.drawArc(
      Rect.fromCircle(center: center, radius: radius),
      -pi / 2,
      totalAngle,
      false,
      backgroundPaint,
    );

    // 绘制进度圆环
    if (value != null && value! > 0) {
      final progressPaint = Paint()
        ..strokeWidth = strokeWidth
        ..style = PaintingStyle.stroke
        ..strokeCap = strokeCapRound ? StrokeCap.round : StrokeCap.butt;

      if (colors.length == 1) {
        progressPaint.color = colors.first;
      } else {
        final gradient = SweepGradient(
          colors: colors,
          stops: stops,
        );
        progressPaint.shader = gradient.createShader(
          Rect.fromCircle(center: center, radius: radius),
        );
      }

      canvas.drawArc(
        Rect.fromCircle(center: center, radius: radius),
        -pi / 2,
        totalAngle * value!,
        false,
        progressPaint,
      );
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

🎨 主题系统设计

主题切换流程图

flowchart TD
    A[用户触发主题切换] --> B[AppTheme.switchTheme]
    B --> C[更新_themeMode]
    C --> D[通知主题监听器]
    D --> E[UI组件重新构建]
    E --> F[应用新主题样式]

    G[系统主题变化] --> H[监听系统主题]
    H --> I[自动切换主题模式]
    I --> B

    J[自定义主题配置] --> K[setCustomConfig]
    K --> L[更新_customConfig]
    L --> D

主题配置示例

/// 自定义主题配置示例
class CustomThemeConfig implements IThemeConfig {
  const CustomThemeConfig();

  @override
  Color get primary => const Color(0xFF6366F1); // 靛蓝色

  @override
  Color get secondary => const Color(0xFF8B5CF6); // 紫色

  @override
  Color get success => const Color(0xFF10B981); // 绿色

  @override
  Color get error => const Color(0xFFEF4444); // 红色

  @override
  Color get warning => const Color(0xFFF59E0B); // 橙色

  @override
  Color get info => const Color(0xFF3B82F6); // 蓝色

  @override
  Color get text => const Color(0xFF1F2937); // 深灰色

  @override
  Color get textSecondary => const Color(0xFF6B7280); // 中灰色

  @override
  Color get background => const Color(0xFFF9FAFB); // 浅灰色

  @override
  Color get surface => Colors.white;

  @override
  Color get divider => const Color(0xFFE5E7EB); // 分割线颜色

  @override
  Color get border => const Color(0xFFD1D5DB); // 边框颜色

  @override
  Color get disabled => const Color(0xFF9CA3AF); // 禁用颜色

  @override
  Color get disabledLight => const Color(0xFFF3F4F6); // 浅禁用颜色

  @override
  Color get tips => const Color(0xFFF59E0B); // 提示颜色

  @override
  Color get dialogText => const Color(0xFF1F2937); // 对话框文本

  @override
  Color get lightBlue => const Color(0xFFDBEAFE); // 浅蓝色

  @override
  Color get lighterBlue => const Color(0xFFEFF6FF); // 更浅蓝色

  @override
  Color get lightGray => const Color(0xFFF3F4F6); // 浅灰色
}

/// 使用自定义主题
void setupCustomTheme() {
  AppTheme.instance.setCustomConfig(const CustomThemeConfig());
}

⚡ 性能优化策略

1. 组件优化

/// 使用 const 构造函数优化
class UIButton extends BaseWidget {
  const UIButton({
    super.key,
    required this.text,
    this.onPressed,
    this.type = UIButtonType.primary,
    this.size = UIButtonSize.medium,
    this.isLoading = false,
    this.isDisabled = false,
    this.icon,
    this.fullWidth = false,
  });

  // 使用 const 构造函数可以避免不必要的重建
  static const UIButton primaryButton = UIButton(
    text: 'Primary',
    type: UIButtonType.primary,
  );
}

2. 列表优化

/// 使用 ListView.builder 优化长列表
class UIListView extends StatelessWidget {
  const UIListView({
    super.key,
    required this.itemCount,
    required this.itemBuilder,
    this.separatorBuilder,
    this.padding,
    this.physics,
  });

  final int itemCount;
  final Widget Function(BuildContext context, int index) itemBuilder;
  final Widget Function(BuildContext context, int index)? separatorBuilder;
  final EdgeInsetsGeometry? padding;
  final ScrollPhysics? physics;

  @override
  Widget build(BuildContext context) {
    return ListView.separated(
      padding: padding,
      physics: physics,
      itemCount: itemCount,
      itemBuilder: itemBuilder,
      separatorBuilder: separatorBuilder ?? (context, index) => const SizedBox.shrink(),
    );
  }
}

3. 动画优化

/// 使用 AnimationController 优化动画性能
class _UIToolTipState extends State<UIToolTip>
    with TickerProviderStateMixin {
  late AnimationController _animationController;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      duration: widget.config.animationDuration,
      vsync: this,
    );
    _animation = CurvedAnimation(
      parent: _animationController,
      curve: Curves.easeInOut,
    );
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }
}

🧪 测试与质量保证

1. 单元测试示例

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_ui_components/flutter_ui_components.dart';

void main() {
  group('UIButton Tests', () {
    testWidgets('应该渲染带有正确文本的按钮', (tester) async {
      // Arrange
      const buttonText = '测试按钮';

      // Act
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: UIButton(
              text: buttonText,
              onPressed: () {},
            ),
          ),
        ),
      );

      // Assert
      expect(find.text(buttonText), findsOneWidget);
    });

    testWidgets('禁用状态下按钮应该不可点击', (tester) async {
      // Arrange
      bool wasPressed = false;

      // Act
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: UIButton(
              text: '禁用按钮',
              onPressed: () => wasPressed = true,
              isDisabled: true,
            ),
          ),
        ),
      );

      await tester.tap(find.text('禁用按钮'));
      await tester.pump();

      // Assert
      expect(wasPressed, false);
    });

    testWidgets('加载状态下应该显示加载指示器', (tester) async {
      // Act
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: UIButton(
              text: '加载按钮',
              onPressed: () {},
              isLoading: true,
            ),
          ),
        ),
      );

      // Assert
      expect(find.byType(CircularProgressIndicator), findsOneWidget);
    });
  });
}

2. Widget 测试示例

void main() {
  group('UIToolTip Tests', () {
    testWidgets('应该显示工具提示内容', (tester) async {
      // Arrange
      const tooltipContent = '这是一个工具提示';

      // Act
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: UIToolTip(
              content: const Text(tooltipContent),
              child: const Text('悬停我'),
            ),
          ),
        ),
      );

      // 触发悬停
      await tester.longPress(find.text('悬停我'));
      await tester.pumpAndSettle();

      // Assert
      expect(find.text(tooltipContent), findsOneWidget);
    });

    testWidgets('应该根据配置显示箭头', (tester) async {
      // Act
      await tester.pumpWidget(
        MaterialApp(
          home: Scaffold(
            body: UIToolTip(
              content: const Text('带箭头的提示'),
              config: const UIToolTipConfig(showArrow: true),
              child: const Text('悬停我'),
            ),
          ),
        ),
      );

      await tester.longPress(find.text('悬停我'));
      await tester.pumpAndSettle();

      // Assert
      expect(find.byType(CustomPaint), findsWidgets);
    });
  });
}

📱 使用示例

1. 基础使用

import 'package:flutter/material.dart';
import 'package:flutter_ui_components/flutter_ui_components.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter UI Components Demo',
      theme: AppTheme.instance.lightTheme,
      darkTheme: AppTheme.instance.darkTheme,
      themeMode: AppTheme.instance.themeMode,
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('UI Components Demo'),
        actions: [
          IconButton(
            icon: const Icon(Icons.brightness_6),
            onPressed: () {
              // 切换主题
              final currentMode = AppTheme.instance.themeMode;
              final newMode = currentMode == ThemeMode.light
                  ? ThemeMode.dark
                  : ThemeMode.light;
              AppTheme.instance.switchTheme(newMode);
            },
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 按钮组件示例
            _buildSectionTitle('按钮组件'),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                UIButton(
                  text: '主要按钮',
                  onPressed: () => _showToast('主要按钮被点击'),
                  type: UIButtonType.primary,
                ),
                UIButton(
                  text: '成功按钮',
                  onPressed: () => _showToast('成功按钮被点击'),
                  type: UIButtonType.success,
                ),
                UIButton(
                  text: '警告按钮',
                  onPressed: () => _showToast('警告按钮被点击'),
                  type: UIButtonType.warning,
                ),
                UIButton(
                  text: '错误按钮',
                  onPressed: () => _showToast('错误按钮被点击'),
                  type: UIButtonType.error,
                ),
                UIButton(
                  text: '轮廓按钮',
                  onPressed: () => _showToast('轮廓按钮被点击'),
                  type: UIButtonType.outline,
                ),
                UIButton(
                  text: '文本按钮',
                  onPressed: () => _showToast('文本按钮被点击'),
                  type: UIButtonType.text,
                ),
              ],
            ),

            const SizedBox(height: 24),

            // 卡片组件示例
            _buildSectionTitle('卡片组件'),
            UIDefaultCard(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    '卡片标题',
                    style: TextStyle(
                      fontSize: 18,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 8),
                  const Text('这是一个卡片组件的示例内容。'),
                  const SizedBox(height: 16),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                      UIButton(
                        text: '取消',
                        type: UIButtonType.text,
                        onPressed: () {},
                      ),
                      const SizedBox(width: 8),
                      UIButton(
                        text: '确定',
                        onPressed: () {},
                      ),
                    ],
                  ),
                ],
              ),
            ),

            const SizedBox(height: 24),

            // 工具提示组件示例
            _buildSectionTitle('工具提示组件'),
            Center(
              child: UIToolTip(
                content: const Text('这是一个工具提示'),
                config: const UIToolTipConfig(
                  direction: UIToolTipDirection.down,
                  backgroundColor: Colors.black87,
                  textColor: Colors.white,
                ),
                child: UIButton(
                  text: '悬停查看提示',
                  onPressed: () {},
                ),
              ),
            ),

            const SizedBox(height: 24),

            // 进度指示器示例
            _buildSectionTitle('进度指示器'),
            Center(
              child: Column(
                children: [
                  UIGradientCircularProgressIndicator(
                    radius: 50,
                    value: 0.7,
                    colors: const [
                      Colors.blue,
                      Colors.purple,
                      Colors.pink,
                    ],
                    child: const Center(
                      child: Text(
                        '70%',
                        style: TextStyle(
                          fontSize: 16,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                  ),
                  const SizedBox(height: 16),
                  UIProgressIndicator(
                    value: 0.6,
                    backgroundColor: Colors.grey[300]!,
                    valueColor: Colors.blue,
                  ),
                ],
              ),
            ),

            const SizedBox(height: 24),

            // 标签组件示例
            _buildSectionTitle('标签组件'),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                UITagView(
                  text: '默认标签',
                  onTap: () => _showToast('默认标签被点击'),
                ),
                UITagView(
                  text: '主要标签',
                  type: UITagType.primary,
                  onTap: () => _showToast('主要标签被点击'),
                ),
                UITagView(
                  text: '成功标签',
                  type: UITagType.success,
                  onTap: () => _showToast('成功标签被点击'),
                ),
                UITagView(
                  text: '警告标签',
                  type: UITagType.warning,
                  onTap: () => _showToast('警告标签被点击'),
                ),
                UITagView(
                  text: '错误标签',
                  type: UITagType.error,
                  onTap: () => _showToast('错误标签被点击'),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSectionTitle(String title) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 16),
      child: Text(
        title,
        style: const TextStyle(
          fontSize: 20,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }

  void _showToast(String message) {
    // 这里可以使用 UIToast 组件显示提示
    debugPrint('Toast: $message');
  }
}

2. 高级使用

/// 自定义主题配置
class MyCustomThemeConfig implements IThemeConfig {
  const MyCustomThemeConfig();

  @override
  Color get primary => const Color(0xFF6366F1);

  @override
  Color get secondary => const Color(0xFF8B5CF6);

  @override
  Color get success => const Color(0xFF10B981);

  @override
  Color get error => const Color(0xFFEF4444);

  @override
  Color get warning => const Color(0xFFF59E0B);

  @override
  Color get info => const Color(0xFF3B82F6);

  @override
  Color get text => const Color(0xFF1F2937);

  @override
  Color get textSecondary => const Color(0xFF6B7280);

  @override
  Color get background => const Color(0xFFF9FAFB);

  @override
  Color get surface => Colors.white;

  @override
  Color get divider => const Color(0xFFE5E7EB);

  @override
  Color get border => const Color(0xFFD1D5DB);

  @override
  Color get disabled => const Color(0xFF9CA3AF);

  @override
  Color get disabledLight => const Color(0xFFF3F4F6);

  @override
  Color get tips => const Color(0xFFF59E0B);

  @override
  Color get dialogText => const Color(0xFF1F2937);

  @override
  Color get lightBlue => const Color(0xFFDBEAFE);

  @override
  Color get lighterBlue => const Color(0xFFEFF6FF);

  @override
  Color get lightGray => const Color(0xFFF3F4F6);
}

/// 应用初始化
void main() {
  // 设置自定义主题
  AppTheme.instance.setCustomConfig(const MyCustomThemeConfig());

  runApp(const MyApp());
}

/// 响应式布局示例
class ResponsiveLayout extends StatelessWidget {
  const ResponsiveLayout({super.key});

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        if (constraints.maxWidth > 1200) {
          return _buildDesktopLayout();
        } else if (constraints.maxWidth > 800) {
          return _buildTabletLayout();
        } else {
          return _buildMobileLayout();
        }
      },
    );
  }

  Widget _buildDesktopLayout() {
    return Row(
      children: [
        Expanded(
          flex: 2,
          child: _buildSidebar(),
        ),
        Expanded(
          flex: 5,
          child: _buildMainContent(),
        ),
        Expanded(
          flex: 2,
          child: _buildRightPanel(),
        ),
      ],
    );
  }

  Widget _buildTabletLayout() {
    return Column(
      children: [
        _buildHeader(),
        Expanded(
          child: Row(
            children: [
              Expanded(
                flex: 1,
                child: _buildSidebar(),
              ),
              Expanded(
                flex: 2,
                child: _buildMainContent(),
              ),
            ],
          ),
        ),
      ],
    );
  }

  Widget _buildMobileLayout() {
    return Column(
      children: [
        _buildHeader(),
        Expanded(
          child: _buildMainContent(),
        ),
        _buildBottomNavigation(),
      ],
    );
  }

  Widget _buildSidebar() {
    return UIDefaultCard(
      child: Column(
        children: [
          UIButton(
            text: '菜单项 1',
            type: UIButtonType.text,
            fullWidth: true,
            onPressed: () {},
          ),
          UIButton(
            text: '菜单项 2',
            type: UIButtonType.text,
            fullWidth: true,
            onPressed: () {},
          ),
          UIButton(
            text: '菜单项 3',
            type: UIButtonType.text,
            fullWidth: true,
            onPressed: () {},
          ),
        ],
      ),
    );
  }

  Widget _buildMainContent() {
    return UIDefaultCard(
      child: Column(
        children: [
          const Text(
            '主要内容区域',
            style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 16),
          UIProgressIndicator(
            value: 0.7,
            backgroundColor: Colors.grey[300]!,
            valueColor: Colors.blue,
          ),
          const SizedBox(height: 16),
          Wrap(
            spacing: 8,
            children: [
              UITagView(text: '标签 1'),
              UITagView(text: '标签 2', type: UITagType.primary),
              UITagView(text: '标签 3', type: UITagType.success),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildRightPanel() {
    return UIDefaultCard(
      child: Column(
        children: [
          const Text('右侧面板'),
          const SizedBox(height: 16),
          UIGradientCircularProgressIndicator(
            radius: 30,
            value: 0.8,
            colors: const [Colors.blue, Colors.purple],
          ),
        ],
      ),
    );
  }

  Widget _buildHeader() {
    return Container(
      padding: const EdgeInsets.all(16),
      child: Row(
        children: [
          const Text(
            '应用标题',
            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
          ),
          const Spacer(),
          UIButton(
            text: '设置',
            type: UIButtonType.outline,
            onPressed: () {},
          ),
        ],
      ),
    );
  }

  Widget _buildBottomNavigation() {
    return Container(
      padding: const EdgeInsets.all(16),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          UIButton(
            text: '首页',
            type: UIButtonType.text,
            onPressed: () {},
          ),
          UIButton(
            text: '搜索',
            type: UIButtonType.text,
            onPressed: () {},
          ),
          UIButton(
            text: '我的',
            type: UIButtonType.text,
            onPressed: () {},
          ),
        ],
      ),
    );
  }
}

🚀 总结与展望

项目亮点

  1. 完整的组件体系:覆盖了 Flutter 应用开发中的大部分 UI 需求
  2. 灵活的主题系统:支持浅色/深色主题切换,易于定制
  3. 优秀的性能表现:使用 const 构造函数和优化策略
  4. 完善的测试覆盖:确保组件的稳定性和可靠性
  5. 清晰的架构设计:模块化设计,易于维护和扩展

技术特色

  • SOLID 原则:遵循单一职责、开放封闭等设计原则
  • 响应式设计:适配不同屏幕尺寸和设备类型
  • 自定义绘制:部分组件使用 CustomPainter 实现复杂效果
  • 动画优化:使用 AnimationController 实现流畅动画
  • 状态管理:支持多种状态管理方案

未来规划

  1. 组件扩展:继续添加更多实用组件
  2. 主题丰富:提供更多预设主题方案
  3. 国际化支持:完善多语言支持
  4. 性能优化:持续优化组件性能
  5. 文档完善:提供更详细的开发文档

Flutter UI Components 致力于为 Flutter 开发者提供一套完整、高质量、易用的 UI 组件解决方案。通过模块化的架构设计、完善的主题系统和优秀的性能表现,帮助开发者快速构建美观、流畅的 Flutter 应用。

项目由于目前还在完善中,未来会在合适的时候更新出来,感兴趣的同学敬请期待

📚 相关资源


❌