Flutter 底部列表抽屉,三阶滑动 , 支持列表Sliver布局

Flutter 底部是列表的抽屉,三阶滑动,支持列表Sliver布局,Head布局可以触发抽屉滑动, 内部的Sliver布局也可以触发抽屉滑动;

抽屉在最大高度时,Sliver布局可以滑动,其他高度会触发抽屉滑动;

 

import 'package:flutter/material.dart';


class SliverPanel3Controller {
  _SliverPanel3ViewState? onState;

  _addState(_SliverPanel3ViewState? onState){
    this.onState = onState;
  }

  void setPanel3State(Panel3State state){
    onState?.setPanel3state(state);
  }

  Panel3State getPanel3State(){
    return onState?._panel3state.value ?? Panel3State.CENTER;
  }

}

enum Panel3State { OPEN, CENTER, CLOSE, EXIT }

class SliverPanel3View extends StatefulWidget {
  final double heightOpen; //展开高度
  final double heightCenter; //中间高度
  final double heightClose; //闭合高度
  final Widget headWidget; //标题布局
  final Widget Function(ScrollController sc , ScrollPhysics? physics)? bodyWidget; //内容布局
  final Panel3State initPanel3state; //初始状态
  final Color backColor; //背景色
  final SliverPanel3Controller? sliverPanel3Controller; //控制器

  const SliverPanel3View(
      {Key? key,
      this.heightOpen = 600,
      this.heightCenter = 360,
      this.heightClose = 100,
      required this.headWidget,
      required this.bodyWidget,
        this.sliverPanel3Controller,
      this.initPanel3state = Panel3State.CENTER,
      this.backColor = Colors.transparent})
      : super(key: key);

  @override
  State<SliverPanel3View> createState() => _SliverPanel3ViewState();
}


class _SliverPanel3ViewState extends State<SliverPanel3View> {
  double heightClose = 100;
  double heightCenter = 360;
  double heightOpen = 600;
  final ValueNotifier<Panel3State> _panel3state = ValueNotifier(Panel3State.CENTER);
  SliverPanel3Controller? sliverPanel3Controller;
  ScrollController _sc = ScrollController();
  ScrollPhysics? _physics;

  @override
  void initState() {
    super.initState();
    heightClose = widget.heightClose;
    heightCenter = widget.heightCenter;
    heightOpen = widget.heightOpen;
    _panel3state.value = widget.initPanel3state;
    sliverPanel3Controller = widget.sliverPanel3Controller;
    sliverPanel3Controller?._addState(this);

  }

  void setPanel3state(Panel3State s){
    _panel3state.value = s;
  }

  double panelHeight() {

    if(_panel3state.value == Panel3State.OPEN){
      _physics = const AlwaysScrollableScrollPhysics();
    }else{
      _physics = const NeverScrollableScrollPhysics();
    }

    if (_panel3state.value == Panel3State.OPEN) {
      return heightOpen;
    } else if (_panel3state.value == Panel3State.CENTER) {
      return heightCenter;
    } else if (_panel3state.value == Panel3State.CLOSE) {
      return heightClose;
    } else {
      return 0;
    }
  }

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder(
      valueListenable: _panel3state,
      builder: (context, state, child) {
        return AnimatedContainer(
          color: widget.backColor,
          duration: const Duration(milliseconds: 220),
          width: double.infinity,
          height: panelHeight(),
          child: Column(
            children: [HeadView(),  Expanded(child: BodyView())],
          ),
        );
      },
    );
  }

  double pointerMove = 0;
  bool isCan = true;
  double scOffset = 0;

  Widget HeadView() {
    return Listener(
      onPointerDown: (e) {
        pointerMove = e.position.dy;
        isCan = true;
      },
      onPointerMove: (e) {
        if (e.position.dy - pointerMove > 36 && isCan) {
          // print("手指下滑触发 -- ");
          isCan = false;
          if (_panel3state.value == Panel3State.OPEN) {
            _panel3state.value = Panel3State.CENTER;
          } else if (_panel3state.value == Panel3State.CENTER) {
            _panel3state.value = Panel3State.CLOSE;
          }
        } else if (e.position.dy - pointerMove < -36 && isCan) {
          // print("手指上滑触发 -- ");
          isCan = false;
          if (_panel3state.value == Panel3State.CLOSE) {
            _panel3state.value = Panel3State.CENTER;
          } else if (_panel3state.value == Panel3State.CENTER) {
            _panel3state.value = Panel3State.OPEN;
          }
        }
      },
      onPointerUp: (e) {
        isCan = true;
      },
      child: widget.headWidget,
    );
  }


  Widget BodyView() {
    return Listener(
      onPointerDown: (e) {
        pointerMove = e.position.dy;
        isCan = true;
      },
      onPointerMove: (e) {
          scOffset = _sc.hasClients ? _sc.offset : 0; //滑动控制器是否绑定

        if (e.position.dy - pointerMove > 36 && isCan) {
          // print("手指下滑触发 -- ");
          isCan = false;
          if (_panel3state.value == Panel3State.OPEN && scOffset <= 0) {
            _panel3state.value = Panel3State.CENTER;
          } else if (_panel3state.value == Panel3State.CENTER) {
            _panel3state.value = Panel3State.CLOSE;
          }
        } else if (e.position.dy - pointerMove < -36 && isCan) {
          // print("手指上滑触发 -- ");
          isCan = false;
          if (_panel3state.value == Panel3State.CLOSE) {
            _panel3state.value = Panel3State.CENTER;
          } else if (_panel3state.value == Panel3State.CENTER) {
            _panel3state.value = Panel3State.OPEN;
          }
        }
      },
      onPointerUp: (e) {
        isCan = true;
      },
      child: Container(child: widget.bodyWidget!(_sc , _physics),),
    );
  }
}

使用:

import 'package:flutter/material.dart';


openTestSlidingPanel3Page(BuildContext context) {
  Navigator.push(context, CupertinoPageRoute(builder: (BuildContext context) {
    return TestSlidingPanel3Page();
  }));
}

class TestSlidingPanel3Page extends StatefulWidget {
  const TestSlidingPanel3Page({Key? key}) : super(key: key);

  @override
  State<TestSlidingPanel3Page> createState() => _TestSlidingPanel3PageState();
}

class _TestSlidingPanel3PageState extends State<TestSlidingPanel3Page> {

  SliverPanel3Controller slidingPanel3Controller = SliverPanel3Controller();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        children: [
          InkWell(
            onTap: (){
              slidingPanel3Controller.setPanel3State(Panel3State.CLOSE);
            },
            child: Container(
              width: double.infinity,
              height: double.infinity,
              color: Colors.blue.withAlpha(88),
            ),
          ),
          Align(
              alignment: AlignmentDirectional.bottomCenter,
              child: SliverPanel3View(
                heightClose: ScreenUtils.getDip(108),
                heightCenter: ScreenUtils.getDip(330),
                heightOpen: ScreenUtils.getDip(680),
                headWidget: headView(),
                bodyWidget: (ScrollController sc , ScrollPhysics? physics){
                  return BodyView(sc , physics);
                },
                sliverPanel3Controller: slidingPanel3Controller,
                initPanel3state: Panel3State.CENTER,

              )),
        ],
      ),
    );
  }

  Widget headView() {
    return Container(
      height: ScreenUtils.getDip(108),
      width: ScreenUtils.getScreenWidth(),
      padding: ScreenUtils.edge(20, 10, 20, 0),
      decoration: const BoxDecoration(
          color: Colors.white,
          borderRadius: BorderRadius.only(
            topLeft: Radius.circular(8),
            topRight: Radius.circular(8),
          )),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          cuttingHorizontalSliver(),
          SizedBox(
            height: ScreenUtils.getDip(12),
          ),
          InkWell(
            onTap: (){
              slidingPanel3Controller.setPanel3State(Panel3State.EXIT);
              LocationPluginUtils.get().then((value) {
                SGMLogger.info(value);
              });
            },
            child: Container(
              margin: ScreenUtils.getMargin20(value: 4),
              width: double.infinity,
              height: ScreenUtils.getDip(40),
              alignment: AlignmentDirectional.center,
              decoration: BoxDecoration(
                color: color_f6f6f6,
                borderRadius: BorderRadius.circular(ScreenUtils.getDip(20)),
              ),
              child: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Icon(Icons.search , color: Colors.grey.withAlpha(80),size: 20,),
                  const SizedBox(
                    width: 5,
                  ),
                  Text(
                    'Search',
                    style: TextStyle(
                        color: color_00131D.withOpacity(.3),
                        fontSize: ScreenUtils.getSp(14),
                        fontWeight: FontWeight.w400),
                  ),
                ],
              ),
            ),
          ),
          Container(
            height: ScreenUtils.getDip(40),
            child: Row(
              children: [
                Icon(Icons.ac_unit , color: Colors.redAccent ,size: 20,),
                const SizedBox(
                  width: 8,
                ),
                Expanded(
                  child: Text(
                    '惊喜来袭!!! 森林公园免门票 快冲...',
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(
                        color: Colors.redAccent,
                        height: 1.2,
                        fontSize: ScreenUtils.getSp(14),
                        fontWeight: FontWeight.w400),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget BodyView(ScrollController sc ,  ScrollPhysics? physics) {

    // return Container(height: 999,width: 380, color: Colors.yellowAccent,);

    Widget _itemView(int i) {
      return Container(
        color: color_fff,
        padding: ScreenUtils.edge(20, 21, 20, 0),
        height: ScreenUtils.getDip(94),
        child: Row(
          children: [
            Icon(Icons.add_a_photo_rounded , color: Colors.orange, size: 66,),
            const SizedBox(
              width: 18,
            ),
            Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    '森林公园游乐场',
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(
                        color: color_00131D,
                        fontSize: ScreenUtils.getSp(16),
                        fontWeight: FontWeight.w500),
                  ),
                  const SizedBox(
                    height: 8,
                  ),
                  Text(
                    '景点热度🔥 610$i',
                    style: TextStyle(
                        color: color_6A6E74,
                        fontSize: ScreenUtils.getSp(14),
                        fontWeight: FontWeight.w400),
                  ),
                  Spacer(),
                  LineCuttingHorizontal(colorLine: color_ededed),
                ],
              ),
            ),
          ],
        ),
      );
    }

    return ListView.builder(
      controller: sc,
      physics: physics,
      padding: EdgeInsets.zero,
      itemCount: 19,
      itemBuilder: (BuildContext context, int i) {
        return _itemView(i);
      },
    );
  }
}