好久没更新了,最近发生了太多的事儿,疫情还没有过去,目前还在武汉,相比较2个月前现在武汉的情况好太多了,虽然我居住小区还是封锁中,进出没那么自由,但人们心逐渐的平静下来,不再恐慌和担忧。我也静下心来写点东西,最近一个月写了一些Flutter相关的项目,遇到的问题不少,索性来个小总结吧,那就废话不多说,直接上干货。
ListView 撑开剩余空间
很多情况下,我们需要ListView撑开剩余空间,这个时候在外面套一个Expanded就可以做到了。如下面这个张图里显示的:
核心代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Container( child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ topicTitle(), Expanded( child: tListView.separated( shrinkWrap: true, itemCount: 10, itemBuilder: (BuildContext context, int position) { }, separatorBuilder: (BuildContext context, int position) { return Padding(padding: EdgeInsets.only(top: 12),); }, ), ), commitBtn(), ], ), );
|
底部弹框的使用
showModalBottomSheet
底部弹框的使用以及给底部弹框切圆角,可以这么做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| showModalBottomSheet( context: context, isDismissible: false, enableDrag: false, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( topLeft: Radius.circular(24), topRight: Radius.circular(24)), ), builder: (BuildContext context) { return RadioPicker( start: () {}, stop: () {}, cancel: () {}, title: recordDuration(), data: vd, dataArr: [], ); });
|
再来看看效果:
点击背景关闭键盘
一般用于页面有文本输入框要输入内容时,键盘弹起挡住页面其它内容,加上一下这一句就OK了。
1 2 3 4
| GestureDetector( behavior: HitTestBehavior.translucent, onTap: () => FocusScope.of(context).requestFocus(FocusNode()), child: Container());
|
输入框的光标问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| TextField( maxLines: 3, scrollPadding: EdgeInsets.fromLTRB(0, 0, 0, 0), expands: false, onChanged: (text) { _inputText = text }, controller: TextEditingController.fromValue( TextEditingValue( text: _inputText, selection: TextSelection.fromPosition( TextPosition( affinity: TextAffinity.downstream, offset: _inputText.length ) ) ) ), decoration: InputDecoration( contentPadding: EdgeInsets.fromLTRB(0, 0, 0, 0), hintText: "请输入作业项", hintStyle: TextStyle(fontSize: 15, color: Color(0xFF808080)), border: InputBorder.none), )
|
父组件动态更新子组件显示的内容
我这里是更新底部弹框的内容,使用的是 ValueNotifier
,实现代码如下:
1 2 3
| class ValueNotifierData extends ValueNotifier<String> { ValueNotifierData(value) : super(value); }
|
子组件中的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class RadioPicker extends StatefulWidget { ValueNotifierData data;
@override _RadioPickerState createState() => _RadioPickerState();
RadioPicker({Key key, this.data}) : super(key: key); }
class _RadioPickerState extends State<RadioPicker> { String title;
@override void initState() { super.initState(); widget.data.addListener(_handleValueChanged); }
void _handleValueChanged() { setState(() { title = widget.data.value; }); } }
|
父组件中动态去修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class _ListenTaskPageState extends State<ListenTaskPage> { ValueNotifierData vd = ValueNotifierData('00:00:00'); }
class _ListenTaskPageState extends State<ListenTaskPage> { @override void initState() { super.initState(); }
_start() async { vd.value = "xxxxxx"; }
void recordMp3() { showModalBottomSheet( context: context, isDismissible: false, builder: (BuildContext context) { return RadioPicker( data: vd, ); }); } }
|
全局通知事件
全局通知事件会使用到ChangeNotifier
。这里使用场景是当收到推送的消息后更新学生任务角标数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
class NotificationProvider with ChangeNotifier { String studentID; int dotCount;
void updateStudentData(String studentID, int dotCount) async { this.studentID = studentID; this.dotCount = dotCount;
notifyListeners(); } }
|
当然别忘了在 main()
函数里面加一下, ChangeNotifierProvider
套上:
1 2 3 4 5 6 7 8 9 10
| void main() async { runApp(MultiProvider( providers: [ ChangeNotifierProvider(create: (_){ return NotificationProvider(); }), ], child: MyApp(), )); }
|
和原生进行通信
在flutter代码里打开 iOS 原生Apple Music 为例:
dart
代码实现:
1 2 3 4 5 6 7 8 9 10 11
| if (Platform.isIOS) { const platform = const MethodChannel('joewang'); var result; try { result = await platform.invokeMethod('openMedia'); print(result); return Future.value(result); } on PlatformException catch (e) { return Future.error(e.toString()); } }
|
iOS 原生代码(OC)实现:
.h
文件:
1 2 3 4 5 6 7 8 9 10 11 12
| #import <Foundation/Foundation.h> #import <Flutter/Flutter.h> #import <UIKit/UIKit.h> #import <MediaPlayer/MediaPlayer.h>
NS_ASSUME_NONNULL_BEGIN
@interface FlutterNativePlugin : NSObject<FlutterPlugin>
@end
NS_ASSUME_NONNULL_END
|
.m
文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #import "FlutterNativePlugin.h" @interface FlutterNativePlugin() <UIImagePickerControllerDelegate, MPMediaPickerControllerDelegate> @property (nonatomic) UIViewController *viewController; @property (nonatomic) MPMediaPickerController *audioPickerController; @end
@implementation FlutterNativePlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar { FlutterMethodChannel *channel = [FlutterMethodChannel methodChannelWithName:@"joewang" binaryMessenger:[registrar messenger]]; UIViewController *viewController = [UIApplication sharedApplication].delegate.window.rootViewController; FlutterNativePlugin *instance = [[FlutterNativePlugin alloc]initWithViewController:viewController]; [registrar addMethodCallDelegate:instance channel:channel]; }
- (instancetype)initWithViewController:(UIViewController *)viewController { self = [super init]; if(self) { self.viewController = viewController; } return self; }
- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { if ([call.method isEqualToString:@"openMedia"]) { self.audioPickerController = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeMusic]; self.audioPickerController.delegate = self; self.audioPickerController.showsCloudItems = NO; self.audioPickerController.allowsPickingMultipleItems = NO; self.audioPickerController.modalPresentationStyle = UIModalPresentationCurrentContext; [self.viewController presentViewController:self.audioPickerController animated:YES completion:nil]; } }
|
测试的时候记得iPhone要安装 Apple Music App哦,本人曾遇到过,还花了半天的时间找原因。
网络请求loading的显示问题。
通常我们在发出请求之前,显示loading,请求完成后再 dismiss
掉这个loading。类似于这样的代码:
1 2 3 4 5 6 7 8 9
| Future uploadMp3() async { Loading(context).show(); HttpUtil.post("url", "params", (value) { Loading(context).dismiss(); }, (value) { Loading(context).dismiss(); }); }
|
一般来讲,这样 show
和 dismiss
loading不会有什么问题,但是当网络请求在某一个异步回调函数中发出时,此时我们的loading调用 dismiss
好像不管用了。如:选择音频文件上传,在拿到音频文件完成后,我需要先异步来获取音频文件的时长,再将音频文件通过接口上传,那么此时会发现一直在loading。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| try { await FilePicker.getFilePath(type: FileType.audio).then((value) { print("value = $value"); if (value == null) { return; } _getDuration(value).then((duration) => { print(duration) }).whenComplete(() { uploadMp3(value, context); }); }); } catch (e) { ZhituoxinToast.showToast(msg: e.toString()); }
|
解决办法:在 Loading
找到 showDialog<void>()
。在调用 showDialog
时传入 GlobalKey
,通过 GlobalKey
去获取正确的 context
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| class Loading { _Body _dialog; VoidCallback timeOutHandler; int timeOut; String msg;
static GlobalKey loadingGlobalKey = GlobalKey(); }
void show() { showDialog<dynamic>( context: _context, barrierDismissible: false, builder: (BuildContext context) { return WillPopScope( key: loadingGlobalKey, onWillPop: () { return Future.value(_barrierDismissible); }, child: Dialog(child: Container()), ); }, ); }
|
网络请求里面调用的时候这样写:
1 2 3 4 5 6 7 8 9 10 11 12
| Future uploadMp3() async { Loading(context).show(); HttpUtil.post("url", "params", (value) { Loading(context).dismiss(); if (Loading.loadingGlobalKey.currentContext != null) { Navigator.of(Loading.loadingGlobalKey.currentContext)?.pop; } }, (value) { }); }
|
Text
设置ellipsis
不起作用?
TextOverflow.ellipsis
不起作用?可以这么做:最外层用Row或者Flex包裹,然后再用Expanded包一层就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| Row( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ Image.asset( 'assets/images/add_student_header_icon.png', width: 19, height: 19 ), Padding( padding: EdgeInsets.only(right: 10),), Expanded( child: Text( "听闻远方有你,动身跋涉千里, 我吹过你吹过的风,这算不算相拥, 我踏过你走过的路,这算不算相逢, 我还是喜欢你,认真且怂,从一而终。", textAlign: TextAlign.left, maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 14, color: Color(0xFF808080))), ) ], )
|
欢迎关注公众号:flutter_todo,有更多技术干货和学习资源教程分享。