Сообщество IT-специалистов
Ответы на любые вопросы об IT
Профессиональное развитие в IT
Удаленная работа для IT-специалистов
import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( darkTheme: ThemeData.dark(), themeMode: ThemeMode.dark, home: const TelegramProfilePage(), ); } } class TelegramProfilePage extends StatefulWidget { const TelegramProfilePage({super.key}); @override State<TelegramProfilePage> createState() => _TelegramProfilePageState(); } class _TelegramProfilePageState extends State<TelegramProfilePage> with SingleTickerProviderStateMixin { final ScrollController _controller = ScrollController(); double maxHeight = 450.0; double standartHeight = 240; double closedHeight = 102; late AnimationController _animationController; late Animation<double> _maxHeightAnimation; @override void initState() { super.initState(); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); _maxHeightAnimation = Tween<double>( begin: standartHeight, end: maxHeight, ).animate( CurvedAnimation( parent: _animationController, curve: Curves.easeInOut, ), ); _controller.addListener(() { if (_controller.offset > 10 && _animationController.isCompleted) { _animationController.reverse(); } else if (_controller.offset <= 10 && _animationController.isDismissed) { // _animationController.forward(); } }); } @override Widget build(BuildContext context) { return Scaffold( body: CustomScrollView( keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, controller: _controller, physics: const BouncingScrollPhysics(), slivers: [ SliverPersistentHeader( pinned: true, floating: false, delegate: _TelegramAppBarDelegate( animation: _maxHeightAnimation, standartHeight: closedHeight, onStretchTrigger: () async { SchedulerBinding.instance.addPostFrameCallback((_) { _animationController.forward(); }); }, ), ), SliverList( delegate: SliverChildBuilderDelegate( (context, index) { return ListTile( title: Text("Item $index"), ); }, childCount: 50, ), ), ], ), ); } @override void dispose() { _animationController.dispose(); super.dispose(); } } class _TelegramAppBarDelegate extends SliverPersistentHeaderDelegate { final Animation<double> animation; final double standartHeight; final Future<void> Function() onStretchTrigger; _TelegramAppBarDelegate({ required this.animation, required this.standartHeight, required this.onStretchTrigger, }); @override double get minExtent => standartHeight; @override double get maxExtent => animation.value; @override OverScrollHeaderStretchConfiguration? get stretchConfiguration => OverScrollHeaderStretchConfiguration( stretchTriggerOffset: 140, onStretchTrigger: onStretchTrigger); @override Widget build( BuildContext context, double shrinkOffset, bool overlapsContent) { // Расчет размера шрифта на основе текущей высоты final double fontSize = (animation.value - shrinkOffset) .clamp(standartHeight, maxExtent) // Ограничение значений .map(standartHeight, maxExtent, 16, 30); // Перевод высоты в размер шрифта return Container( color: Colors.blue, child: Stack( fit: StackFit.expand, children: [ Positioned( bottom: 16, left: 0, right: 0, child: Center( child: Text( "Telegram Profile Header", style: TextStyle( fontSize: fontSize, color: Colors.white, ), ), ), ), ], ), ); } @override bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { return true; } } // Добавим метод расширения для map значений extension DoubleMapping on double { double map(double inMin, double inMax, double outMin, double outMax) { return outMin + (outMax - outMin) * ((this - inMin) / (inMax - inMin)).clamp(0.0, 1.0); } }