https://flutterchina.club/widgets/input/
Flutter入门学习记录【四】
表单
- Form
final _formKey = GlobalKey<FormState>();
//
Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
TextFormField(
decoration: const InputDecoration(
hintText: 'Enter your email',
),
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
TextFormField(
decoration: const InputDecoration(
hintText: 'Enter your email password',
),
validator: (value) {
if (value.isEmpty) {
return 'Please enter your password';
}
return null;
},
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: RaisedButton(
onPressed: () {
// Validate will return true if the form is valid, or false if
// the form is invalid.
if (_formKey.currentState.validate()) {
// Process data.
}
},
child: Text('Submit'),
),
),
],
),
)
点击按钮,autovalidate为false的情况,执行_formKey.currentState.validate()会对上边的TextFormField的内容进行验证,如果不符合验证条件就会显示错误的提示文字
image.png参数解析
const Form({
Key key,
@required this.child,
this.autovalidate = false,
this.onWillPop,
this.onChanged,
})
/// If true, form fields will validate and update their error text
/// immediately after every change. Otherwise, you must call
/// [FormState.validate] to validate.
//默认是false,你必须手动调用FormState的vallidate方法才会执行判断,如果为true,获取焦点就会自动做判断的
final bool autovalidate;
/// Called when one of the form fields changes.
///任一一个FormField发生改变的时候都会回调这里,好像没啥用
/// In addition to this callback being invoked, all the form fields themselves
/// will rebuild.
final VoidCallback onChanged;
/// Enables the form to veto attempts by the user to dismiss the [ModalRoute]
/// that contains the form.
///简单点说,就是执行页面后退功能的时候也就是pop当前页面的时候,根据返回结果来决定是否 阻止后退,为false阻止,为true不阻止
/// If the callback returns a Future that resolves to false, the form's route
/// will not be popped.
final WillPopCallback onWillPop;
//比如
onWillPop: (){
return Future.value(false);
}
TextFormField
这个和普通的TextFiled参数差不多
有些区别主要就是回调了,比如
ValueChanged<String> onChanged,
GestureTapCallback onTap,
VoidCallback onEditingComplete,
ValueChanged<String> onFieldSubmitted,
FormFieldSetter<String> onSaved,
FormFieldValidator<String> validator,
List<TextInputFormatter> inputFormatters,
RawKeyboardListener
交互
- LongPressDraggable
长按可以拖动
const LongPressDraggable({
Key key,
@required Widget child,//默认显示的widget
@required Widget feedback,//长按以后跟随手指移动的widget
T data,//到时候传给一个DragTarget的控件,用来判断是否接收
Axis axis,
Widget childWhenDragging,//可有可无,长按触发以后用来替换默认的child的
Offset feedbackOffset = Offset.zero,
DragAnchor dragAnchor = DragAnchor.child,//锚点
int maxSimultaneousDrags,
VoidCallback onDragStarted,
DraggableCanceledCallback onDraggableCanceled,//同下,也有对应的数据
DragEndCallback onDragEnd,//这个回调里会有偏移量,速率数据
VoidCallback onDragCompleted,
this.hapticFeedbackOnStart = true,
bool ignoringFeedbackSemantics = true,
})
- 关联的DragTarget
const DragTarget({
Key key,
@required this.builder,
this.onWillAccept,// 回调里返回的参数就是上边那个LongPressDraggable里data,当那个拖动到这个控件范围的时候,返回true表示接收,会存到build的回调参数candidateData,返回false会存到build的回调参数rejectedData里
this.onAccept,//松开手指,上边那个onWillAccept返回true的话会走这里,否则走下边那个
this.onLeave,//另外手指从target范围移出去以后也会走这里
})
简单测试demo
var _dragResult="drag to here please";
DragTarget(
builder: (BuildContext context, List<String> candidateData,
List<dynamic> rejectedData) {
print(
'builder==========${candidateData.length}==========${rejectedData.length}');
if (candidateData.length > 0) {
_dragResult="you'are accepted,please release your finger";
}else if(rejectedData.length>0){
_dragResult="you'are rejected,please release your finger";
}
return Container(
width: 240,
height: 100,
alignment: Alignment.center,
child: Text(_dragResult,style: _defaultStyle,),
decoration: BoxDecoration(
border: Border.all(color: Colors.deepPurpleAccent)),
);
},
onWillAccept: (willAccept) {
print('onWillAccept=========$willAccept');
return willAccept == "test";
},
onAccept: (accept) {
print('accept==========$accept');
_dragResult="accept:$accept";
},
onLeave: (leave) {
print('leave============$leave');
_dragResult="leave:$leave";
},
),
image.png
LongPressDraggable触发以后DragTarget就会执行一次builder,完事手指移动到DragTarget范围的时候会走onWillAccept方法,返回true的话会继续走builder,当手指移出DragTarget范围的时候会执行onLeave,完事继续builder
没搞懂rejectedData这个集合啥时候会有数据
- GestureDetector
想给widget添加触摸事件可以用这个,我一般也就加个点击事件,用onTap即可
- Dismissible
可以拖动的控件,最多拖动距离是控件自身的宽或者高
const Dismissible({
@required Key key,
@required this.child,
this.background,
this.secondaryBackground,
this.confirmDismiss,//根据返回的结果来决定是否dismiss
this.onResize,//确定dismiss以后会回调这里N次
this.onDismissed,//控件彻底消失以后回调
this.direction = DismissDirection.horizontal,//可以滑动的方向
this.resizeDuration = const Duration(milliseconds: 300),
this.dismissThresholds = const <DismissDirection, double>{},//阀值默认0.4也就是拖动40%的距离
this.movementDuration = const Duration(milliseconds: 200),
this.crossAxisEndOffset = 0.0,//偏移量,高度或者宽度【根据主轴来定】的多少倍
this.dragStartBehavior = DragStartBehavior.start,
})
/// A widget that is stacked behind the child. If secondaryBackground is also
/// specified then this widget only appears when the child has been dragged
/// down or to the right.
//往右往上滑动的时候底层显示的widget,如果secondary没设置,往左往下也是这个
final Widget background;
/// A widget that is stacked behind the child and is exposed when the child
/// has been dragged up or to the left. It may only be specified when background
/// has also been specified.
//往左往下滑动的时候显示的wiget,如果设置了这个,上边那个background必须设置
//assert(secondaryBackground == null || background != null),
final Widget secondaryBackground;
/// Defines the end offset across the main axis after the card is dismissed.
///滑到最后横轴的偏移量,
/// If non-zero value is given then widget moves in cross direction depending on whether
/// it is positive or negative.
final double crossAxisEndOffset;
demo
Text("place holder start",style: _defaultStyle,),
Dismissible(
key: Key("dismiss"),
child: Text("Dismissible",style: TextStyle(color: Colors.deepPurple, fontSize: 25,
backgroundColor: Colors.lightBlue)),
confirmDismiss: (dismissDirection) {
print('confirmDismiss===========${dismissDirection}');
return Future.value(true);
},
onDismissed: (dismissDirection) {
print('onDismissed===========${dismissDirection}');
},
crossAxisEndOffset: 2,
background: Text("background",style: _defaultStyle,),
secondaryBackground: Text("secondaryBackground",style: _defaultStyle,),
),
Text("place holder end",style: _defaultStyle,),
看图可以发现:
background或者secondaryBackground的宽高由child来决定的,图上明显少了个字母d
crossAxisEndOffset:我们这里是水平拖动,这个偏移是垂直方向的,child高度的2倍,如果是负的,是往上偏移
初始样子.png
拖动到最终的样子.png
异步 Widgets
- FutureBuilder
const FutureBuilder({
Key key,
this.future,//一个future到时候返回的数据类型也是T
this.initialData,//初始化数据,泛型T
@required this.builder,//构建widget
})
demo
就是个textview,初始化显示loading,过5秒显示success
FutureBuilder(
builder: (context, snapshot) {
print('build=========${snapshot.connectionState}===${snapshot.data}');
//当然可以根据连接状态返回不同的widget
return Text("${snapshot.data}");
},
initialData: "loading",
future: Future.delayed(Duration(seconds: 5), () {//延迟5秒模拟异步操作
return "success";
}),
),
- StreamBuilder
看起来和上边那个差不多,参数都差不多
StreamBuilder(builder: (context, snapshot){
print('build=========${snapshot.connectionState}===${snapshot.data}');
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.none: return Text('Select lot');
case ConnectionState.waiting: return Text('Awaiting bids...');
case ConnectionState.active: return Text('\$${snapshot.data}');
case ConnectionState.done: return Text('\$${snapshot.data} (closed)');
}
return null; // unreachable
},stream: _createStream(),),
Stream<int> _createStream() {
return Stream.periodic(Duration(seconds: 4),(computationCount){
return computationCount;
});
}
网友评论