在 Telegram 应用程序中,实现了一个有趣的功能:它包含一个
BottomSheet
,当向上滑动时,当它到达距顶部特定距离时,会增加其标题的高度并显示 AppBar
。
我无法使用
DraggableScrollableSheet
和 ChatGPT
来实现此行为。下面,我提供了示例代码以及说明我的想法的图像。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Draggable Bottom Sheet',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: DraggableBottomSheetExample(),
);
}
}
class DraggableBottomSheetExample extends StatefulWidget {
@override
_DraggableBottomSheetExampleState createState() =>
_DraggableBottomSheetExampleState();
}
class _DraggableBottomSheetExampleState
extends State<DraggableBottomSheetExample> with TickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _textSizeAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 300),
);
_textSizeAnimation = Tween<double>(begin: 24.0, end: 48.0).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInOut,
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onScroll(double offset) {
if (offset >= 0.8 && !_controller.isAnimating && !_controller.isCompleted) {
_controller.forward();
} else if (offset < 0.8 && !_controller.isAnimating && _controller.isCompleted) {
_controller.reverse();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Draggable Bottom Sheet Example'),
),
body: Stack(
children: <Widget>[
Center(
child: ElevatedButton(
onPressed: () {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (BuildContext context) {
return DraggableScrollableSheet(
initialChildSize: 0.3,
minChildSize: 0.1,
maxChildSize: 0.8,
builder: (context, scrollController) {
scrollController.addListener(() {
_onScroll(scrollController.position.pixels /
scrollController.position.maxScrollExtent);
});
return Container(
color: Colors.blueGrey[200],
child: SingleChildScrollView(
controller: scrollController,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Text(
'Draggable Bottom Sheet',
style: TextStyle(
fontSize: _textSizeAnimation.value,
fontWeight: FontWeight.bold,
),
);
},
),
SizedBox(height: 16),
Text(
'Swipe up to expand or down to collapse.',
style: TextStyle(fontSize: 16),
),
SizedBox(height: 16),
// Add more content here
Container(
height: 500,
color: Colors.blue[100],
),
],
),
),
),
);
},
);
},
);
},
child: Text('Show Draggable Bottom Sheet'),
),
),
],
),
);
}
}
这是一个 ExpandableBottomSheet,当它接近顶部时,它将转换为 AppBar。
import 'package:flutter/material.dart';
void main() {
runApp(const MaterialApp(home: TelegramBottomSheetDemo()));
}
class TelegramBottomSheetDemo extends StatelessWidget {
const TelegramBottomSheetDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Get Public Link Bot')),
body: Center(
child: ElevatedButton(
onPressed: () => showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) => const ExpandableBottomSheet(),
),
child: const Text('Open Sheet'),
),
),
);
}
}
class ExpandableBottomSheet extends StatefulWidget {
const ExpandableBottomSheet({super.key});
@override
State<ExpandableBottomSheet> createState() => _ExpandableBottomSheetState();
}
class _ExpandableBottomSheetState extends State<ExpandableBottomSheet> {
double _height = 0.5;
bool get _isExpanded => _height > 0.9;
void _handleDrag(DragUpdateDetails details) {
setState(() {
_height -= details.primaryDelta! / MediaQuery.of(context).size.height;
_height = _height.clamp(0.3, 1.0);
});
}
void _handleDragEnd(DragEndDetails details) {
setState(() {
if (_height >= 0.9) {
_height = 1.0;
}
});
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onVerticalDragUpdate: _handleDrag,
onVerticalDragEnd: _handleDragEnd,
child: AnimatedContainer(
duration: const Duration(milliseconds: 100),
height: MediaQuery.of(context).size.height * _height,
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.vertical(
top: Radius.circular(_isExpanded ? 0 : 20),
),
),
child: Column(
children: [
_SheetHeader(
isExpanded: _isExpanded,
onCollapse: () {
Navigator.of(context).pop();
// Or you can collapse the sheet instead of popping
// setState(() => _height = 0.5);
},
),
const Expanded(
child: _SheetContent(),
),
],
),
),
);
}
}
class _SheetHeader extends StatelessWidget {
const _SheetHeader({
required this.isExpanded,
required this.onCollapse,
});
final bool isExpanded;
final VoidCallback onCollapse;
@override
Widget build(BuildContext context) {
return Container(
height:
isExpanded ? kToolbarHeight + MediaQuery.of(context).padding.top : 60,
padding: EdgeInsets.only(
top: isExpanded ? MediaQuery.of(context).padding.top : 0,
),
child: Stack(
children: [
if (!isExpanded)
Center(
child: Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(2),
),
),
),
if (isExpanded)
AppBar(
title: const Text('Gallery'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: onCollapse,
),
),
],
),
);
}
}
class _SheetContent extends StatelessWidget {
const _SheetContent();
@override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.zero,
itemCount: 50,
itemBuilder: (_, index) => ListTile(
title: Text('Item ${index + 1}'),
subtitle: Text('Subtitle ${index + 1}'),
),
);
}
}