Flutter 个人主页实践笔记

user_profile 项目为例,梳理暗黑主题、NestedScrollView 嵌套滚动及网格布局等常用技巧。示例代码:github.com/wutao23yzd/...


1 整体结构速览

主页入口是 UserProfilePage → UserProfileView

dart 复制代码
class UserProfileView extends StatefulWidget { ... }

class _UserProfileViewState extends State<UserProfileView>
    with SingleTickerProviderStateMixin {

  @override
  Widget build(BuildContext context) {
    return AppScaffold(
      body: DefaultTabController(
        length: 2,
        child: NestedScrollView(
          controller: _nestedScrollController,
          headerSliverBuilder: ...,
          body: const TabBarView(
            children: [PostsPage(), UserProfileMentionedPostsPage()],
          ),
        ),
      ),
    );
  }
}
  • AppScaffold:项目自定义的外壳,负责 SafeArea、沉浸式系统栏、焦点回收等通用行为。
  • NestedScrollView:实现顶部资料区 + TabBar + 子页面(网格 / 空态)的多层滚动。

2 暗黑主题:用 FlexColorScheme 与扩展函数

2.1 基础主题

dart 复制代码
class AppDarkTheme  {
  const AppDarkTheme();

  ThemeData get theme => FlexThemeData.dark(
        scheme: FlexScheme.custom,
        darkIsTrueBlack: true,
        colors: FlexSchemeColor.from(
          brightness: Brightness.dark,
          primary: AppColors.white,
          appBarColor: AppColors.transparent,
          swapOnMaterial3: true,
        ),
        useMaterial3: true,
      ).copyWith(
        textTheme: const AppDarkTheme().textTheme,
        iconTheme: const IconThemeData(color: AppColors.white),
      );
}
  • 纯黑 (darkIsTrueBlack) + Material 3,适配 OLED。
  • 通过 copyWith 单点覆盖 textTheme / iconTheme,保持全局一致色系。

2.2 自定义颜色获取

lib/packages/app_ui/text_style_extension.dart 中为 BuildContext 扩展了适配方法:

dart 复制代码
extension BuildContextX on BuildContext {
  bool get isLight => theme.brightness == Brightness.light;
  Color customAdaptiveColor({Color? light, Color? dark}) =>
      isDark ? (light ?? AppColors.white) : (dark ?? AppColors.black);
}

当你在控件里写 context.customAdaptiveColor(light: grey, dark: black),暗/亮主题下会自动切换。

2.3 系统状态栏 & 导航栏

_MaterialScaffold 结尾包裹:

dart 复制代码
.withAdaptiveSystemTheme(context);

内部通过 AnnotatedRegion<SystemUiOverlayStyle> 将不同平台 + 主题的状态栏样式抽象成常量:

dart 复制代码
context.isLight
  ? SystemUiOverlayTheme.androidLightSystemBarTheme
  : SystemUiOverlayTheme.androidDarkSystemBarTheme

3 NestedScrollView 多层滚动避坑指南

3.1 吸收 / 注入重叠

顶部区使用 SliverOverlapAbsorber ,子 Tab 使用 SliverOverlapInjector,两者一一对应:

dart 复制代码
headerSliverBuilder: (context, _) => [
  SliverOverlapAbsorber(
    handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
    sliver: MultiSliver( ... ),
  ),
],

子页面:

dart 复制代码
SliverOverlapInjector(
  handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),

这样就能让内部 CustomScrollView 正确计算可滚动范围,解决 "抖动 / 顶部遮挡" 问题。

3.2 固定 TabBar

dart 复制代码
SliverPersistentHeader(
  pinned: true,
  delegate: _SliverAppBarDelegate(
    const TabBar(tabs: [ ... ]),
  ),
)

minExtent == maxExtent,即始终保持同一高度。pinned: true 让 TabBar 吸顶;但若外层路由为根页,SliverAppBar 设置 floating,即可实现 下拉出现 / 上推隐藏 的丝滑效果。


4 SliverGrid 实现九宫格图片流

dart 复制代码
SliverGrid.builder(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
    mainAxisSpacing: 2,
    crossAxisSpacing: 2,
  ),
  itemCount: 7,
  itemBuilder: (context, index) => PostSmallImage(index: index),
),
  • crossAxisCount: 3 从视觉上对齐 Instagram / 小红书风格。
  • PostSmallImage 根据设备 pixelRatio 计算合适图片尺寸,防止超分辨率加载。
dart 复制代码
final screenWidth = (MediaQuery.of(context).size.width - 2.0) / 3;
final pixelRatio = MediaQuery.devicePixelRatioOf(context);
final size = min((screenWidth * pixelRatio) ~/ 1, 1920);

如果你想改成瀑布流,只需替换为 SliverWaterfallFlow 之类组件即可。


5 可点击渐变:Tappable.faded

按钮不仅可点,还能在按下时渐隐:

dart 复制代码
FadeTransition(opacity: _animation, child: child)

FadeStrength 枚举把透明度抽象成 sm / md / lg,代码阅读体验更友好:

dart 复制代码
enum FadeStrength { sm(.2), md(.4), lg(1); ... }

在 UI 中使用:

dart 复制代码
Tappable.faded(
  fadeStrength: FadeStrength.sm,
  borderRadius: BorderRadius.circular(6),
  backgroundColor: effectiveColor,
  child: Text('编辑资料'),
)

6 简单而纯粹的 Bloc

UserProfileBloc 当前仅保存状态,后续若接入 API,可在 on<Event> 里调用 emit(state.copyWith(...)) 完成刷新。初始值:

dart 复制代码
const UserProfileState.initial()
  : this._(
      user: User(name: '钢铁侠', fullName: '钢铁侠·斯塔克'),
      postsCount: 7,
      followersCount: 10,
      followingsCount: 20,
    );

页面侧获取数据极简:

dart 复制代码
final user = context.select((UserProfileBloc b) => b.state.user);

Bloc + context.select 能精准订阅字段,避免整树重建。


7 总结

本篇以 UserProfile 代码为蓝本,梳理了:

  1. 暗黑主题FlexThemeData.dark + 扩展色值 + 系统栏适配
  2. 多层滚动NestedScrollView + SliverOverlapAbsorber / Injector + 固定 TabBar
  3. 网格布局SliverGrid 九宫格图片流与分辨率优化
  4. 交互体验Tappable.faded 渐隐反馈
  5. 状态管理:轻量 Bloc 读取 / 更新

希望对你在实际项目中落地这些模式有所帮助。如果想进一步探索,可以考虑:

  • PostsPage 引入懒加载分页
  • UserProfileBloc 拆分为多事件、多状态
  • 针对不同屏幕尺寸自动切换列数或使用 SliverResponsiveGrid

Happy Coding!

相关推荐
淹没2 小时前
🚀 告别复杂的HTTP模拟!HttpHook让Dart应用测试变得超简单
android·flutter·dart
愿天深海7 小时前
Flutter 提取图像主色调 ColorScheme.fromImageProvider
android·前端·flutter
梦想改变生活1 天前
《Flutter篇第一章》基于GetX 和 Binding、Dio 实现的 Flutter UI 架构
flutter·ui·架构
耳東陈1 天前
[重磅发布] Flutter Chen Generator 必备脚本工具
flutter
亿刀1 天前
【学习VPN之路】NET技术
android·flutter
浅蓝色1 天前
Flutter平台判断问题,并适配鸿蒙
flutter
RaidenLiu1 天前
Skia与Impeller的对比测试
flutter
JarvanMo1 天前
使用 Flutter Lints 提升你的 Flutter 代码:更整洁、更安全、更快速的应用
flutter