likes
comments
collection
share

【Flutter】如何实现一个拖拽关闭/展开的下拉列表

作者站长头像
站长
· 阅读数 12

题记

如题,这是我们需要实现的效果图,我们需要flutter 中将其封装成一个小组件。

效果需求是,可以通过拖动区域拖动。

  1. 实现向上拖动变成列表模式
  2. 向下拖动变成地图模式
  3. 列表保持原有的下拉刷新,上拉加载更多
  4. 需要有中间过渡动画 【Flutter】如何实现一个拖拽关闭/展开的下拉列表

分析

  1. 首先像这种组件一般是用在层叠布局Stack中,所以我们可以用 Position来做组件的容器。
  2. 既然用Position做了容器,那就可以通过控制Position.top来控制列表的高度。
  3. 然后只需要将拖动区域绑定有一个手势检测来实现拖动效果就可以了。
  4. 最后的最后,将Position换成 AnimationPosition来实现动画效果。

一、极少的代码完整实现

import 'package:flutter/material.dart';

class DraggablePositionPanel extends StatefulWidget {
  const DraggablePositionPanel({Key? key, required this.child, this.top = 0})
      : super(key: key);

  final double top;
  final Widget child;

  @override
  State<DraggablePositionPanel> createState() => _DraggablePositionPanelState();
}

class _DraggablePositionPanelState extends State<DraggablePositionPanel> {
  double? dy;

  bool showAnimation = false;

  @override
  Widget build(BuildContext context) {
    const barHeight = 4.0;
    const barPadding = 8.0;
    return AnimatedPositioned(
        duration:
            showAnimation ? const Duration(milliseconds: 200) : Duration.zero,
        top: dy ?? widget.top,
        left: 0,
        right: 0,
        bottom: 0,
        child: Container(
          decoration: const BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.only(
                topLeft: Radius.circular(4), topRight: Radius.circular(4)),
          ),
          child: Column(
            children: [
              //滚动区域
              GestureDetector(
                behavior: HitTestBehavior.opaque,
                onVerticalDragUpdate: (detail) {
                  setState(() {
                    showAnimation = false;
                    dy = detail.globalPosition.dy <= widget.top
                        ? widget.top
                        : detail.globalPosition.dy;
                  });
                },
                onVerticalDragEnd: (detail) {
                  final size = MediaQuery.of(context).size;
                  final threshold = (size.height - widget.top) / 2;
                  setState(() {
                    dy = dy != null && dy! <= threshold
                        ? widget.top
                        : size.height - barHeight - (barPadding * 2);
                    showAnimation = true;
                  });
                },
                child: Center(
                  child: Padding(
                    padding: const EdgeInsets.symmetric(vertical: barPadding),
                    child: Container(
                      width: 48,
                      height: barHeight,
                      decoration: BoxDecoration(
                        color: const Color(0xffc2c2c2),
                        borderRadius: BorderRadius.circular(barHeight),
                      ),
                    ),
                  ),
                ),
              ),
              Expanded(child: widget.child)
            ],
          ),
        ));
  }
}

细节解读

1、手势检测通过设置 GestureDetectorbehavior: HitTestBehavior.opaque,来扩大拖动生效区域。

 GestureDetector(
     behavior: HitTestBehavior.opaque,
      ...
  )

2、通过设置showAnimation状态值来控制AnimatedPositioned.duration来实现拖动过程实时拖动,松手后直接吸附到顶部或者底部。

如何使用

DraggablePositionPanel(
 top: UIScreen.appBarHeight,//自行控制初始上边距
 child:YOUR_WIDGET(), //传入列表组件
)

Bye~