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);
},
);
}
}