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);
}
}