效果圖
讀完你將學到
- 如何實現一個自定義彈窗,並且封裝成複用元件
- 簡單的父子元件間通訊
- stack、grid網格佈局
- 簡單的自定義toast提示
- 如何獲取一個widget的寬高
- 簡單的入場動畫
開始
如何實現一個自定義彈窗
翻閱文件可得:showGeneralDialog方法,具體引數含義請自行查閱文件, 由上面的效果圖看到,點選輸入資訊,當輸入正確的7位車牌號就自動關閉鍵盤,同時頁面更新使用者輸入的車牌號。那麼現在可以確定的是元件要有車牌號,鍵盤關閉事件兩個引數。直接上程式碼:
class PlateNoKeyboard {
final BuildContext context;
final String plateNo;
final Function onClose;
PlateNoKeyboard({
@required this.context,
@required this.plateNo,
@required this.onClose,
});
Future<bool> show() async {
return await showGeneralDialog(
context: context,
pageBuilder: (BuildContext buildContext, Animation<double> animation,
Animation<double> secondaryAnimation) {
return _buildContent;
},
barrierDismissible: false,
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
barrierColor: Colors.black.withOpacity(0.5), // 設定背景顏色
transitionDuration: Duration(milliseconds: 400), // 設定彈窗進場時間
transitionBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) =>
fromBottom(animation, secondaryAnimation, child),
);
}
Widget get _buildContent {
// 改變車牌號需要重新整理頁面,所以這裡必須返回一個StatefulWidget包裹的內容
return KeyboardContent(plateNo: plateNo, onClose: onClose, itemClick: itemClick,);
}
// 從下往上彈出動畫效果
fromBottom(Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
}
複製程式碼
上面的程式碼就是該元件的整體程式碼架構,接下來講下_buildContent內容。 上面程式碼註釋有寫到,由於在點選按鈕的時候上面有個皮膚顯示輸入的結果,所以這裡必須要用到StatefulWidget。
class KeyboardContent extends StatefulWidget {
final String plateNo;
final Function onClose;
const KeyboardContent({
Key key,
@required this.plateNo,
@required this.onClose,
}): super(key: key);
@override
_KeyboardContentState createState() => _KeyboardContentState();
}
複製程式碼
class _KeyboardContentState extends State<KeyboardContent> {
String _plate;
void initState() {
super.initState();
_plate = widget.plateNo; //初始化下傳進來的車牌號
}
}
複製程式碼
簡單的父子元件間通訊
傳進來的onClose回撥事件監聽_plate車牌位數變為7位時,呼叫即可
if (_plate.length == 7) {
// 車牌號達到七位數自動關閉鍵盤
widget.onClose(_plate); //事件回撥
Navigator.pop(context); //關閉彈窗
}
複製程式碼
grid網格佈局
鍵盤的按鈕佈局用到了GridView.count,藉助它可以很方便的實現一個像鍵盤這樣的大小一致的標準網格佈局
GridView.count(
crossAxisSpacing: 7, // 縱向間隔
mainAxisSpacing: 6, // 橫向間隔
crossAxisCount: 6, // 設定每行個數
childAspectRatio: 1.24, // 元素大小比例,該元件child元素設定寬高無效,而是由該引數比例進行設定
children: []
)
複製程式碼
這裡的渲染chidren時有個小技巧,如果只是渲染陣列裡面的資料不需要獲取下標,推薦用
data.map((item) {
return widget
}).toList();
複製程式碼
如果需要獲取下標,可以用
List.generate(data.length, (index) {
return widget;
})
複製程式碼
額,這裡用網格佈局其實遇到了一個問題:這裡按鈕用的是FlatButton,但是在網格佈局中發現沒有了點選墨水散開效果(InkWell),移除到別的widget卻正常,由文章開頭效果圖可以看到按鈕點選時沒有一個正常的點選效果的,基於此只好給它加個toast提示當前點選的是哪個。
實現Toast提示
這個提示位置不是固定的,所以要用stack配合position實現。為了較方便的實現按鈕居中定位(長度)在每個按鈕的上方,toast提示框寬度設為和按鈕一樣的長度。上面的網格佈局有說到每個child的長寬其實不是固定的寬高而是固定一個比例,所以這就要藉助GlobalKey獲取widget的長度了
這裡又遇到一個問題,直接將key放置在鍵盤按鈕的FlatButton中,又會報奇怪的錯誤,只好又折中取其父容器的寬度計算出按鈕的長度,至於toast提示的高度這裡就設定了一個大概合理定值,這個高度多少影響不大
相關toast實現程式碼:
獲取widget寬度值:_bodyKey.currentContext.size.width;
計算toast提示顯示位置函式處理:
最後
也許僅僅增加一個toast提示反饋還不夠?還要增加一個震動反饋效果?那就增加個點選事件回撥吧!這個簡單
class PlateNoKeyboard {
final BuildContext context;
final String plateNo;
final Function onClose;
final Function itemClick; // 新增
PlateNoKeyboard({
@required this.context,
@required this.plateNo,
@required this.onClose,
this.itemClick, // 新增
});
複製程式碼
Widget get _buildContent {
// 改變車牌號需要重新整理頁面,所以這裡必須返回一個StatefulWidget包裹的內容
return KeyboardContent(plateNo: plateNo, onClose: onClose, itemClick: itemClick //新增,);
}
複製程式碼
在點選事件中新增:
widget.itemClick != null ? widget.itemClick() : '';
// 這裡不能使用widget?.itemClick()及widget.itemClick != null && widget.itemClick()寫法,會報錯
複製程式碼
至此,一個元件就完成了!
使用
在頁面中引入
import 'package:app_flutter/widgets/plate_no_keyboard.dart';
複製程式碼
在點選事件中呼叫
PlateNoKeyboard(context: context, plateNo: plateNo, onClose: keyboardClose).show();
複製程式碼
引數 | 含義 |
---|---|
context | Buildcontext |
plateNo | 車牌號 |
onClose | 鍵盤關閉事件回撥 |
itemClick | 按鈕點選事件回撥(可選,用於需要給點選按鈕加震動反饋效果處理) |
函式處理例子
keyboardClose(String plate) {
setState(() {
plateNo = plate;
});
}
複製程式碼
itemClick() {
print('震動一次');
}
複製程式碼