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的內容進行驗證,如果不符合驗證條件就會顯示錯誤的提示文字
參數解析
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";
},
),
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倍,如果是負的,是往上偏移
異步 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;
});
}