Flutter 颤振中的标准底板
我很难在我的应用程序中实现“标准底部工作表”-我指的是底部工作表,其中“标题”可见且可拖动(参考:)。更重要的是:我在任何地方都找不到这样的例子:S.我所希望的结果是通过实现DragableScrollableSheet作为底部工作表来实现的:在Scaffold中(只有该小部件具有initialChildSize),但是没有办法使标题“粘滞”bc所有内容都是可滚动的:/ 我还发现:-像那里的接缝关于“持久的底部工作表”的部分是我正在寻找的,但文章没有详细说明,所以我无法准确地找出实现它的方法,加上评论通常是负面的,所以我猜这不是完全正确的Flutter 颤振中的标准底板,flutter,bottom-sheet,Flutter,Bottom Sheet,我很难在我的应用程序中实现“标准底部工作表”-我指的是底部工作表,其中“标题”可见且可拖动(参考:)。更重要的是:我在任何地方都找不到这样的例子:S.我所希望的结果是通过实现DragableScrollableSheet作为底部工作表来实现的:在Scaffold中(只有该小部件具有initialChildSize),但是没有办法使标题“粘滞”bc所有内容都是可滚动的:/ 我还发现:-像那里的接缝关于“持久的底部工作表”的部分是我正在寻找的,但文章没有详细说明,所以我无法准确地找出实现它的方法,加
有人有任何解决方案吗?:S如果您正在寻找持久的底部表单,请参考下面链接中的源代码
您可以参考\u showBottomSheet()了解您的需求,一些更改将满足您的需求您可以使用堆栈和动画:
class HelloWorldPage extends StatefulWidget {
@override
_HelloWorldPageState createState() => _HelloWorldPageState();
}
class _HelloWorldPageState extends State<HelloWorldPage>
with SingleTickerProviderStateMixin {
final double minSize = 80;
final double maxSize = 350;
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 500))
..addListener(() {
setState(() {});
});
_animation =
Tween<double>(begin: minSize, end: maxSize).animate(_controller);
super.initState();
}
AnimationController _controller;
Animation<double> _animation;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: <Widget>[
Positioned(
bottom: 0,
height: _animation.value,
child: GestureDetector(
onDoubleTap: () => _onEvent(),
onVerticalDragEnd: (event) => _onEvent(),
child: Container(
color: Colors.red,
width: MediaQuery.of(context).size.width,
height: minSize,
),
),
),
],
),
);
}
_onEvent() {
if (_controller.isCompleted) {
_controller.reverse(from: maxSize);
} else {
_controller.forward();
}
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
类HelloWorldPage扩展StatefulWidget{
@凌驾
_HelloWorldPageState createState()=>\u HelloWorldPageState();
}
类_HelloWorldPageState扩展了状态
使用SingleTickerProviderStateMixin{
最终双分钟=80;
最终双倍最大尺寸=350;
void initState(){
_控制器=
AnimationController(vsync:this,duration:duration(毫秒:500))
…addListener(){
setState((){});
});
_动画=
Tween(开始:minSize,结束:maxSize).animate(_controller);
super.initState();
}
动画控制器_控制器;
动画(动画),;
@凌驾
小部件构建(构建上下文){
返回脚手架(
主体:堆栈(
fit:StackFit.expand,
儿童:[
定位(
底部:0,
高度:_animation.value,
儿童:手势检测器(
onDoubleTap:()=>\u OneEvent(),
onVerticalDragEnd:(事件)=>\u onEvent(),
子:容器(
颜色:颜色,红色,
宽度:MediaQuery.of(context).size.width,
高度:minSize,
),
),
),
],
),
);
}
_onEvent(){
如果(_controller.isCompleted){
_控制器。反向(从:最大尺寸);
}否则{
_controller.forward();
}
}
@凌驾
无效处置(){
_controller.dispose();
super.dispose();
}
}
正如@Sergio提出的一些好的替代方案一样,它仍然需要更多的编码才能正常工作,尽管如此,我发现了向上滑动面板,因此任何其他寻求解决方案的人都可以找到它
尽管如此,我还是觉得很奇怪,Flatter中的内置底部表单小部件没有提供创建material.io:S中提到的“标准底部表单”的选项。可以使用
DragableScrollableSheet
实现在material spec中看到的标准底部表单行为
在这里我将详细解释它
步骤1:
定义您的脚手架
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Draggable sheet demo',
home: Scaffold(
///just for status bar color.
appBar: PreferredSize(
preferredSize: Size.fromHeight(0),
child: AppBar(
primary: true,
elevation: 0,
)),
body: Stack(
children: <Widget>[
Positioned(
left: 0.0,
top: 0.0,
right: 0.0,
child: PreferredSize(
preferredSize: Size.fromHeight(56.0),
child: AppBar(
title: Text("Standard bottom sheet demo"),
elevation: 2.0,
)),
),
DraggableSearchableListView(),
],
)),
);
}
}
步骤3:
定义自定义粘性搜索栏
class SearchBar extends StatelessWidget {
final TextEditingController textEditingController;
final ValueNotifier<bool> closeButtonVisibility;
final ValueChanged<String> onSearchSubmit;
final VoidCallback onClose;
const SearchBar({
Key key,
@required this.textEditingController,
@required this.closeButtonVisibility,
@required this.onSearchSubmit,
@required this.onClose,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Container(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 0),
child: Row(
children: <Widget>[
SizedBox(
height: 56.0,
width: 56.0,
child: Material(
type: MaterialType.transparency,
child: InkWell(
child: Icon(
Icons.arrow_back,
color: theme.textTheme.caption.color,
),
onTap: () {
FocusScope.of(context).unfocus();
textEditingController.clear();
closeButtonVisibility.value = false;
onClose();
},
),
),
),
SizedBox(
width: 16.0,
),
Expanded(
child: TextFormField(
onChanged: (value) {
if (value != null && value.length > 0) {
closeButtonVisibility.value = true;
} else {
closeButtonVisibility.value = false;
}
},
onFieldSubmitted: (value) {
FocusScope.of(context).unfocus();
onSearchSubmit(value);
},
keyboardType: TextInputType.text,
textInputAction: TextInputAction.search,
textCapitalization: TextCapitalization.none,
textAlignVertical: TextAlignVertical.center,
textAlign: TextAlign.left,
maxLines: 1,
controller: textEditingController,
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
hintText: "Search here",
),
),
),
ValueListenableBuilder<bool>(
valueListenable: closeButtonVisibility,
builder: (context, value, child) {
return value
? SizedBox(
width: 56.0,
height: 56.0,
child: Material(
type: MaterialType.transparency,
child: InkWell(
child: Icon(
Icons.close,
color: theme.textTheme.caption.color,
),
onTap: () {
closeButtonVisibility.value = false;
textEditingController.clear();
},
),
),
)
: Container();
})
],
),
),
);
}
}
类搜索栏扩展了无状态小部件{
最终文本编辑控制器文本编辑控制器;
最终价值通知人关闭按钮可视性;
搜索提交时更改的最终值;
最终失效;
常量搜索栏({
关键点,
@需要此.textEditingController,
@需要此选项。关闭按钮不可见,
@需要此.onSearchSubmit,
@需要这个.onClose,
}):super(key:key);
@凌驾
小部件构建(构建上下文){
最终主题数据主题=theme.of(上下文);
返回容器(
孩子:填充(
填充:边集。对称(水平:0),
孩子:排(
儿童:[
大小盒子(
身高:56.0,
宽度:56.0,
儿童:材料(
类型:MaterialType.transparency,
孩子:InkWell(
子:图标(
Icons.arrow_back,
颜色:theme.textTheme.caption.color,
),
onTap:(){
(上下文)的焦点范围。取消焦点();
textEditingController.clear();
closeButtonVisibility.value=false;
onClose();
},
),
),
),
大小盒子(
宽度:16.0,
),
扩大(
子项:TextFormField(
一旦更改:(值){
如果(value!=null&&value.length>0){
closeButtonVisibility.value=true;
}否则{
closeButtonVisibility.value=false;
}
},
onFieldSubmitted:(值){
(上下文)的焦点范围。取消焦点();
onSearchSubmit(值);
},
键盘类型:TextInputType.text,
textInputAction:textInputAction.search,
textcapitalize:textcapitalize.none,
textAlignVertical:textAlignVertical.center,
textAlign:textAlign.left,
maxLines:1,
控制器:textEditingController,
装饰:输入装饰(
是的,
边框:InputBorder.none,
hintText:“在此处搜索”,
),
),
),
ValueListenableBuilder(
valueListenable:closeButtonVisibility,
生成器:(上下文、值、子级){
返回值
?尺寸箱(
宽度:56.0,
class SearchBar extends StatelessWidget {
final TextEditingController textEditingController;
final ValueNotifier<bool> closeButtonVisibility;
final ValueChanged<String> onSearchSubmit;
final VoidCallback onClose;
const SearchBar({
Key key,
@required this.textEditingController,
@required this.closeButtonVisibility,
@required this.onSearchSubmit,
@required this.onClose,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Container(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 0),
child: Row(
children: <Widget>[
SizedBox(
height: 56.0,
width: 56.0,
child: Material(
type: MaterialType.transparency,
child: InkWell(
child: Icon(
Icons.arrow_back,
color: theme.textTheme.caption.color,
),
onTap: () {
FocusScope.of(context).unfocus();
textEditingController.clear();
closeButtonVisibility.value = false;
onClose();
},
),
),
),
SizedBox(
width: 16.0,
),
Expanded(
child: TextFormField(
onChanged: (value) {
if (value != null && value.length > 0) {
closeButtonVisibility.value = true;
} else {
closeButtonVisibility.value = false;
}
},
onFieldSubmitted: (value) {
FocusScope.of(context).unfocus();
onSearchSubmit(value);
},
keyboardType: TextInputType.text,
textInputAction: TextInputAction.search,
textCapitalization: TextCapitalization.none,
textAlignVertical: TextAlignVertical.center,
textAlign: TextAlign.left,
maxLines: 1,
controller: textEditingController,
decoration: InputDecoration(
isDense: true,
border: InputBorder.none,
hintText: "Search here",
),
),
),
ValueListenableBuilder<bool>(
valueListenable: closeButtonVisibility,
builder: (context, value, child) {
return value
? SizedBox(
width: 56.0,
height: 56.0,
child: Material(
type: MaterialType.transparency,
child: InkWell(
child: Icon(
Icons.close,
color: theme.textTheme.caption.color,
),
onTap: () {
closeButtonVisibility.value = false;
textEditingController.clear();
},
),
),
)
: Container();
})
],
),
),
);
}
}