Flutter 实现自定义可拖动底板

问题描述 投票:0回答:1

在 Telegram 应用程序中,实现了一个有趣的功能:它包含一个

BottomSheet
,当向上滑动时,当它到达距顶部特定距离时,会增加其标题的高度并显示
AppBar

我无法使用

DraggableScrollableSheet
ChatGPT
来实现此行为。下面,我提供了示例代码以及说明我的想法的图像。

enter image description here

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'),
            ),
          ),
        ],
      ),
    );
  }
}
flutter flutter-animation
1个回答
0
投票

这是一个 ExpandableBottomSheet,当它接近顶部时,它将转换为 AppBar。

Demo of the code

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}'),
      ),
    );
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.