You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在expandedHeight为0时实现SliverAppBar标题从左到居中的滚动动画

实现方案说明

你要的效果不需要自定义AnimatedWidget,也不需要设置非0的expandedHeight,核心逻辑是通过滚动监听获取SliverAppBar的折叠进度,用系统自带的插值方法计算标题的对齐位置,即可实现平滑的移动过渡效果。


完整基础实现示例
import 'package:flutter/material.dart';

class SliverTitleMoveDemo extends StatefulWidget {
  const SliverTitleMoveDemo({super.key});

  @override
  State<SliverTitleMoveDemo> createState() => _SliverTitleMoveDemoState();
}

class _SliverTitleMoveDemoState extends State<SliverTitleMoveDemo> {
  final ScrollController _scrollController = ScrollController();
  // 滚动触发标题移动的阈值,和AppBar高度保持一致即可
  final double _scrollThreshold = kToolbarHeight;

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(() {
      // 滚动时触发刷新
      setState(() {});
    });
  }

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

  @override
  Widget build(BuildContext context) {
    // 计算滚动进度:0为完全未滚动,1为滚动到阈值位置
    double progress = (_scrollController.offset / _scrollThreshold).clamp(0, 1);
    // 插值计算当前标题的对齐位置
    Alignment titleAlignment = Alignment.lerp(Alignment.centerLeft, Alignment.center, progress)!;

    return Scaffold(
      body: CustomScrollView(
        controller: _scrollController,
        slivers: [
          SliverAppBar(
            pinned: true,
            // expandedHeight和toolbarHeight保持一致,无需设置非0的额外高度
            expandedHeight: kToolbarHeight,
            toolbarHeight: kToolbarHeight,
            flexibleSpace: FlexibleSpaceBar(
              titlePadding: const EdgeInsets.symmetric(horizontal: 16),
              title: Align(
                alignment: titleAlignment,
                child: const Text("测试标题"),
              ),
            ),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (context, index) => ListTile(title: Text("列表项 $index")),
              childCount: 50,
            ),
          )
        ],
      ),
    );
  }
}

性能优化方案

如果不想每次滚动都调用setState刷新整个页面,可以用ValueNotifier做局部刷新,性能开销更低:

// 替换进度变量定义
final ValueNotifier<double> _progress = ValueNotifier(0.0);

@override
void initState() {
  super.initState();
  _scrollController.addListener(() {
    // 只更新进度值,不触发全页面刷新
    _progress.value = (_scrollController.offset / _scrollThreshold).clamp(0, 1);
  });
}

// FlexibleSpaceBar部分修改为局部刷新逻辑
flexibleSpace: FlexibleSpaceBar(
  titlePadding: const EdgeInsets.symmetric(horizontal: 16),
  title: ValueListenableBuilder<double>(
    valueListenable: _progress,
    builder: (context, progress, child) {
      Alignment titleAlignment = Alignment.lerp(Alignment.centerLeft, Alignment.center, progress)!;
      return Align(
        alignment: titleAlignment,
        child: child,
      );
    },
    child: const Text("测试标题"),
  ),
)

内容的提问来源于stack exchange,提问作者H Wong

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 19:36:01