实现Flutter SliverAppBar仿Telegram的滚动行为
实现类似Telegram的SliverAppBar滚动效果(Flutter)
Telegram的SliverAppBar核心滚动行为是:展开时显示完整头部(含头像、居中标题),向下滚动时逐渐折叠,最终固定为紧凑的顶部导航栏(标题左移、头像缩小)。以下是具体实现方案:
基础实现(核心配置)
通过SliverAppBar内置属性快速搭建基础折叠效果:
import 'package:flutter/material.dart'; void main() => runApp(const TelegramStyleApp()); class TelegramStyleApp extends StatelessWidget { const TelegramStyleApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: CustomScrollView( slivers: [ SliverAppBar( pinned: true, // 保持AppBar始终固定在顶部 expandedHeight: 200, // 展开时的总高度 backgroundColor: const Color(0xFF0088CC), // Telegram主题蓝 flexibleSpace: FlexibleSpaceBar( centerTitle: true, titlePadding: const EdgeInsets.only(bottom: 16), title: const Text( '联系人', style: TextStyle(color: Colors.white, fontSize: 18), ), // 展开状态下的头部内容 background: Padding( padding: const EdgeInsets.only(bottom: 50), child: Center( child: CircleAvatar( radius: 40, backgroundColor: Colors.white, child: Image.network('https://picsum.photos/200'), ), ), ), ), ), // 滚动内容列表 SliverList( delegate: SliverChildBuilderDelegate( (context, index) => ListTile( leading: const CircleAvatar(child: Icon(Icons.person)), title: Text('联系人 $index'), subtitle: const Text('最近消息'), ), childCount: 50, ), ), ], ), ), ); } }
进阶自定义(精细过渡效果)
要实现Telegram那种标题位置切换、头像大小动态变化的精细效果,可通过LayoutBuilder监听滚动约束,结合状态更新调整组件参数:
import 'package:flutter/material.dart'; void main() => runApp(const TelegramStyleApp()); class TelegramStyleApp extends StatefulWidget { const TelegramStyleApp({super.key}); @override State<TelegramStyleApp> createState() => _TelegramStyleAppState(); } class _TelegramStyleAppState extends State<TelegramStyleApp> { double _avatarRadius = 40; @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( body: CustomScrollView( slivers: [ SliverAppBar( pinned: true, expandedHeight: 200, backgroundColor: const Color(0xFF0088CC), // 通过LayoutBuilder获取实时滚动约束 flexibleSpace: LayoutBuilder( builder: (context, constraints) { // 计算折叠比例:0=完全展开,1=完全折叠 final scrollOffset = constraints.biggest.height - kToolbarHeight; final collapseRatio = 1 - (scrollOffset / (200 - kToolbarHeight)).clamp(0, 1); setState(() { // 动态调整头像大小:从40缩小到30 _avatarRadius = 40 - (collapseRatio * 10); }); return Stack( fit: StackFit.expand, children: [ // 背景层 Container(color: const Color(0xFF0088CC)), // 随滚动缩小的头像 Positioned( left: MediaQuery.of(context).size.width / 2 - _avatarRadius, bottom: 50, child: CircleAvatar( radius: _avatarRadius, backgroundColor: Colors.white, child: Image.network('https://picsum.photos/200'), ), ), // 从居中切换到左对齐的标题 Positioned( bottom: collapseRatio > 0.5 ? 16 : 0, left: collapseRatio > 0.5 ? null : 16, right: collapseRatio > 0.5 ? 0 : null, child: Opacity( opacity: collapseRatio > 0 ? collapseRatio : 1, child: Text( '联系人', style: const TextStyle(color: Colors.white, fontSize: 18), textAlign: collapseRatio > 0.5 ? TextAlign.center : TextAlign.left, ), ), ), ], ); }, ), ), SliverList( delegate: SliverChildBuilderDelegate( (context, index) => ListTile( leading: const CircleAvatar(child: Icon(Icons.person)), title: Text('联系人 $index'), subtitle: const Text('最近消息'), ), childCount: 50, ), ), ], ), ), ); } }
关键注意点
pinned: true是固定导航栏的核心配置,确保AppBar不会完全滚出屏幕- 通过
LayoutBuilder获取的constraints.biggest.height可实时获取当前AppBar高度,以此计算折叠比例 - 动态变化的组件属性需通过
setState触发重绘,实现平滑过渡
内容的提问来源于stack exchange,提问作者Hardik Mehta
相关产品推荐
相关产品推荐

