You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于DraggableScrollableSheet实现页面拖拽过渡动画方案咨询

双向拖拽页面过渡实现方案

问题描述

我有两个页面,需求是:

  • 向下拖拽第一个页面,完成向第二个页面的过渡
  • 第二个页面底部的DraggableScrollableSheet向上拖拽时,能回到第一个页面

目前借助ChatGPT得到的代码仅实现了第一部分功能,无法将Sheet的拖拽动作转化为页面过渡动画。请问该需求是否可行?若可行,应如何实现?

当前代码:

import 'package:flutter/material.dart';

class DragTransitionView extends StatefulWidget {
  @override
  State<DragTransitionView> createState() => _DragTransitionViewState();
}

class _DragTransitionViewState extends State<DragTransitionView>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 300),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _onFirstPageDragUpdate(DragUpdateDetails details) {
    // Dragging down increases the controller value
    if (details.primaryDelta! > 0) {
      _controller.value +=
          details.primaryDelta! / MediaQuery.of(context).size.height;
    }
  }

  void _onFirstPageDragEnd(DragEndDetails details) {
    if (_controller.value > 0.5) {
      _controller.forward(); // Complete transition to the second page
    } else {
      _controller.reverse(); // Return to the first page
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          // Second Page
          FadeTransition(
            opacity: _controller,
            child: AnimatedBuilder(
              animation: _controller,
              builder: (context, child) {
                return Transform.translate(
                  offset: Offset(
                      0,
                      MediaQuery.of(context).size.height *
                          (1 - _controller.value)),
                  child: child,
                );
              },
              child: Container(
                color: Colors.blue,
                child: Stack(
                  children: [
                    Center(
                      child: Text(
                        'Second Page',
                        style: TextStyle(color: Colors.white, fontSize: 24),
                      ),
                    ),
                    // DraggableScrollableSheet
                    DraggableScrollableSheet(
                      initialChildSize: 0.3, // Initial height of the sheet
                      minChildSize: 0.1, // Minimum height
                      maxChildSize: 1.0, // Maximum height
                      builder: (BuildContext context,
                          ScrollController scrollController) {
                        return FadeTransition(
                          opacity: _controller,
                          child: Container(
                            color: Colors.white,
                            child: SingleChildScrollView(
                              controller: scrollController,
                              child: Center(
                                child: Text(
                                  'Drag Up from Here',
                                  style: TextStyle(
                                      color: Colors.black, fontSize: 18),
                                ),
                              ),
                            ),
                          ),
                        );
                      },
                    ),
                  ],
                ),
              ),
            ),
          ),
          // First Page
          GestureDetector(
            onVerticalDragUpdate: _onFirstPageDragUpdate,
            onVerticalDragEnd: _onFirstPageDragEnd,
            child: AnimatedBuilder(
              animation: _controller,
              builder: (context, child) {
                return Transform.translate(
                  offset: Offset(0,
                      MediaQuery.of(context).size.height * _controller.value),
                  child: Opacity(
                    opacity: 1 - _controller.value,
                    child: child,
                  ),
                );
              },
              child: Container(
                color: Colors.red,
                child: Center(
                  child: Text(
                    'First Page',
                    style: TextStyle(color: Colors.white, fontSize: 24),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

可行性说明及实现方案

该需求完全可行,核心思路是将DraggableScrollableSheet的拖拽进度与页面过渡的AnimationController绑定,同时处理拖拽边界和结束时的动画归位逻辑。

修改后的完整代码

import 'package:flutter/material.dart';

class DragTransitionView extends StatefulWidget {
  @override
  State<DragTransitionView> createState() => _DragTransitionViewState();
}

class _DragTransitionViewState extends State<DragTransitionView>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  // 记录Sheet的当前尺寸比例,用于同步到动画控制器
  double _sheetSize = 0.3;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300),
      value: 0.0, // 初始值为0,显示第一页
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  // 处理第一页向下拖拽更新
  void _onFirstPageDragUpdate(DragUpdateDetails details) {
    if (details.primaryDelta! > 0 && _controller.value < 1.0) {
      _controller.value += details.primaryDelta! / MediaQuery.of(context).size.height;
      // 同步Sheet尺寸,确保页面过渡时Sheet处于初始位置
      _sheetSize = 0.3 - (_controller.value * 0.2);
      setState(() {});
    }
  }

  // 处理第一页拖拽结束
  void _onFirstPageDragEnd(DragEndDetails details) {
    if (_controller.value > 0.5) {
      _controller.forward().then((_) {
        setState(() => _sheetSize = 0.3);
      });
    } else {
      _controller.reverse().then((_) {
        setState(() => _sheetSize = 0.3);
      });
    }
  }

  // 处理Sheet拖拽更新
  void _onSheetDragUpdate(double size) {
    // 只有当页面完全切换到第二页时,才允许通过Sheet拖拽返回
    if (_controller.value == 1.0) {
      setState(() => _sheetSize = size);
      // 将Sheet的尺寸变化映射到动画控制器的反向进度
      // Sheet从0.3到1.0对应控制器从1.0到0.0
      _controller.value = 1.0 - ((size - 0.3) / 0.7);
    }
  }

  // 处理Sheet拖拽结束
  void _onSheetDragEnd() {
    // 根据当前控制器值判断是否完成返回动画
    if (_controller.value < 0.5) {
      _controller.reverse();
    } else {
      _controller.forward().then((_) {
        setState(() => _sheetSize = 0.3);
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          // 第二页
          AnimatedBuilder(
            animation: _controller,
            builder: (context, child) {
              return Transform.translate(
                offset: Offset(
                  0,
                  MediaQuery.of(context).size.height * (1 - _controller.value),
                ),
                child: Opacity(
                  opacity: _controller.value,
                  child: child,
                ),
              );
            },
            child: Container(
              color: Colors.blue,
              child: Stack(
                children: [
                  const Center(
                    child: Text(
                      'Second Page',
                      style: TextStyle(color: Colors.white, fontSize: 24),
                    ),
                  ),
                  // 可拖拽Sheet
                  DraggableScrollableSheet(
                    initialChildSize: _sheetSize,
                    minChildSize: 0.1,
                    maxChildSize: 1.0,
                    onDragUpdate: (details) => _onSheetDragUpdate(details.extent),
                    onDragEnd: (details) => _onSheetDragEnd(),
                    builder: (BuildContext context, ScrollController scrollController) {
                      return Container(
                        color: Colors.white,
                        child: SingleChildScrollView(
                          controller: scrollController,
                          child: const Center(
                            child: Text(
                              'Drag Up from Here',
                              style: TextStyle(color: Colors.black, fontSize: 18),
                            ),
                          ),
                        ),
                      );
                    },
                  ),
                ],
              ),
            ),
          ),
          // 第一页
          GestureDetector(
            onVerticalDragUpdate: _onFirstPageDragUpdate,
            onVerticalDragEnd: _onFirstPageDragEnd,
            child: AnimatedBuilder(
              animation: _controller,
              builder: (context, child) {
                return Transform.translate(
                  offset: Offset(
                    0,
                    MediaQuery.of(context).size.height * _controller.value,
                  ),
                  child: Opacity(
                    opacity: 1 - _controller.value,
                    child: child,
                  ),
                );
              },
              child: Container(
                color: Colors.red,
                child: const Center(
                  child: Text(
                    'First Page',
                    style: TextStyle(color: Colors.white, fontSize: 24),
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

关键改动说明

  • 绑定Sheet拖拽与动画控制器:通过DraggableScrollableSheet的onDragUpdate回调,将Sheet的拖拽进度(extent)映射到AnimationController的反向值,实现向上拖拽Sheet时,页面同步过渡回第一页。
  • 同步页面过渡时的Sheet状态:在第一页拖拽过程中,同步调整Sheet的尺寸,避免页面过渡时Sheet位置错乱。
  • 优化拖拽结束逻辑:分别处理第一页和Sheet拖拽结束时的动画归位,根据当前进度判断是完成过渡还是回弹到初始状态。
  • 移除冗余的FadeTransition:将透明度整合到AnimatedBuilder中,简化层级结构。

内容的提问来源于stack exchange,提问作者MKJ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 03:50:54