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

Flutter自定义布局与动画实现可折叠AppBar问题求助

解决可折叠SliverAppBar的自定义布局与动画问题

核心思路

放弃FlexibleSpaceBar的默认title逻辑,通过监听滚动偏移计算折叠进度,自定义flexibleSpace内的布局与动画,实现图片渐隐、文本样式过渡的效果。

实现代码

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:cached_network_image/cached_network_image.dart';

class CollapsibleAppBarPage extends StatefulWidget {
  final YourModel _model; // 替换成你的数据模型类

  const CollapsibleAppBarPage(this._model, {super.key});

  @override
  State<CollapsibleAppBarPage> createState() => _CollapsibleAppBarPageState();
}

class _CollapsibleAppBarPageState extends State<CollapsibleAppBarPage> {
  final ScrollController _scrollController = ScrollController();
  double _collapseProgress = 0.0;
  final double _expandedHeight = Get.height * 0.2;
  final double _toolbarHeight = kToolbarHeight;

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(_updateCollapseProgress);
  }

  void _updateCollapseProgress() {
    // 计算折叠进度:0=完全展开,1=完全折叠
    final scrollOffset = _scrollController.offset;
    final maxScroll = _expandedHeight - _toolbarHeight;
    setState(() {
      _collapseProgress = (scrollOffset / maxScroll).clamp(0.0, 1.0);
    });
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        controller: _scrollController,
        slivers: [
          SliverAppBar(
            pinned: true,
            leading: GestureDetector(
              onTap: () => Get.back(),
              child: const Icon(Icons.arrow_back, color: Colors.black),
            ),
            expandedHeight: _expandedHeight,
            backgroundColor: const Color(0xFFFFF4F4),
            // 自定义flexibleSpace,替代FlexibleSpaceBar的默认布局
            flexibleSpace: FlexibleSpaceBar(
              background: Padding(
                padding: const EdgeInsets.symmetric(horizontal: 16.0),
                child: Column(
                  mainAxisAlignment: MainAxisAlignment.end,
                  children: [
                    Row(
                      children: [
                        // 可动画过渡的文本区域
                        Expanded(
                          child: AnimatedDefaultTextStyle(
                            duration: const Duration(milliseconds: 200),
                            style: TextStyle(
                              color: Colors.black,
                              fontSize: 14 - (4 * _collapseProgress), // 从14过渡到10
                              height: 1.2,
                            ),
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Text("Buffet Deal"),
                                Opacity(
                                  opacity: 1 - _collapseProgress, // 副标题随折叠渐隐
                                  child: Text("Flash Deal", style: TextStyle(fontSize: 10)),
                                ),
                              ],
                            ),
                          ),
                        ),
                        // 渐隐的图片
                        AnimatedOpacity(
                          opacity: 1 - _collapseProgress,
                          duration: const Duration(milliseconds: 200),
                          child: widget._model.imgUrl != null
                              ? CachedNetworkImage(
                                  imageUrl: widget._model.imgUrl!,
                                  width: 80, // 固定图片宽度,可按需调整
                                  height: 60,
                                  fit: BoxFit.cover,
                                )
                              : const SizedBox.shrink(),
                        ),
                        const SizedBox(width: 18),
                      ],
                    ),
                    const SizedBox(height: 16), // 底部内边距,避免内容贴底
                  ],
                ),
              ),
              // 折叠后的标题,随进度渐显
              title: Opacity(
                opacity: _collapseProgress,
                child: const Text(
                  "Buffet Deal",
                  style: TextStyle(color: Colors.black, fontSize: 16),
                ),
              ),
              centerTitle: true,
            ),
          ),
          // 页面内容占位
          SliverToBoxAdapter(
            child: Container(
              height: 1000,
              color: Colors.white,
            ),
          ),
        ],
      ),
    );
  }
}

// 替换成你的实际数据模型
class YourModel {
  final String? imgUrl;
  YourModel({this.imgUrl});
}

关键细节说明

  • 折叠进度计算:通过滚动偏移与expandedHeight - kToolbarHeight的比值,得到0-1之间的进度值,统一控制所有动画参数。
  • 文本过渡:使用AnimatedDefaultTextStyle实现字体大小平滑变化,副标题通过Opacity随折叠进度渐隐;折叠后的独立标题通过Opacity渐显,保证折叠后标题样式符合AppBar常规逻辑。
  • 图片渐隐:通过AnimatedOpacity绑定折叠进度,实现图片从完全显示到隐藏的平滑过渡。
  • 布局对齐:展开时文本左对齐,折叠后标题居中,兼顾展开时的布局需求和折叠后的交互习惯。

内容的提问来源于stack exchange,提问作者Fardeen Khan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 18:09:22