国际化下光标无法对齐
theme: ThemeData(
primaryColor: Style.primaryColor,
accentColor: Style.accentColor,
indicatorColor: Colors.white,
textTheme: TextTheme(
subhead: TextStyle(
textBaseline: TextBaseline.alphabetic) //解决光标跟hinText无法对齐的问题
)
// scaffoldBackgroundColor: Style.bgColor,//全局scaffold背景色
// primarySwatch: Style.primarySwatch,
),
初始化数据存储插件
不进行初始化操作 会在页面渲染时无法获取到存储的数据
void initSp() async {
await SpUtil.getInstance();
if(!mounted) return;
}
dialog在页面未完成加载前弹出会报错 需要在页面完成加载之后再弹出
WidgetsBinding.instance.addPostFrameCallback((_) {
});
嵌套路由
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Expanded(
child: Navigator(
initialRoute: 'addBank',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case 'addBank':
builder = (BuildContext context) => AddBankPage();
break;
case 'selfwithdraw':
builder = (BuildContext context) => WithdrawPage();
break;
}
return new MaterialPageRoute(
builder: builder, settings: settings);
}))
],
),
);
}
路由传参
Navigator.of(context).pushNamed("second ", arguments: " from first page");
class SecondPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
//取出路由参数
String msg = ModalRoute.of(context).settings.arguments as String;
… //数据处理
}
}
路由监听
Widget build(BuildContext context) {
return BotToastInit(
child: MaterialApp(
title: '八八棋牌',
locale: Locale("zh"),
debugShowCheckedModeBanner: false, //是否显示debug模式
localizationsDelegates: [
GlobalWidgetsLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
const FallbackCupertinoLocalisationsDelegate(),
],
navigatorObservers: [
BotToastNavigatorObserver(),
UserNavigatorObserver(context)
],
supportedLocales: [const Locale('zh', 'CH'), const Locale('en', 'US')],
initialRoute: '/',
routes: {
'/MainPage': (ctx) => HomePage(),
},
home: SplashScreen(),
),
);
}
}
class UserNavigatorObserver extends NavigatorObserver {
// BuildContext _context;
AppBloc ab;
UserNavigatorObserver(BuildContext context) {
ab = BlocProvider.of(context);
}
static List> history = >[];
@override
void didPop(Route route, Route previousRoute) {
super.didPop(route, previousRoute);
history.remove(route);
print("UserNavigatorObserver didPush route ${route.settings.toString()} "
"previousRoute ${previousRoute?.settings?.name}");
//String routeName = previousRoute?.settings?.name;
//if (routeName == '/MainPage') {
// ab.sendTriggerAnimate('forward');
//}
///调用Navigator.of(context).pop() 出栈时回调
}
@override
void didPush(Route route, Route previousRoute) {
super.didPush(route, previousRoute);
history.add(route);
// print("UserNavigatorObserver didPush route ${route.settings.toString()} "
// "previousRoute ${previousRoute?.settings?.name}");
// print("UserNavigatorObserver didPush _history: ${history.length}");
// print('==========previousRoute.settings.name====
///调用Navigator.of(context).push(Route()) 进栈时回调
}
@override
void didRemove(Route route, Route previousRoute) {
super.didRemove(route, previousRoute);
history.remove(route);
// print("UserNavigatorObserver didRemove route ${route.settings.toString()} "
// "previousRoute ${previousRoute?.settings?.toString()}");
// print("UserNavigatorObserver didRemove _history: ${history.length}");
///调用Navigator.of(context).removeRoute(Route()) 移除某个路由回调
}
@override
void didReplace({Route newRoute, Route oldRoute}) {
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
history.remove(oldRoute);
history.add(newRoute);
// print(
// "UserNavigatorObserver didReplace route ${newRoute.settings.toString()} "
// "previousRoute ${oldRoute?.settings?.toString()}");
///调用Navigator.of(context).replace( oldRoute:Route("old"),newRoute:Route("new)) 替换路由时回调
}
@override
void didStartUserGesture(Route route, Route previousRoute) {
super.didStartUserGesture(route, previousRoute);
// print(
// "UserNavigatorObserver didStartUserGesture route ${route.settings.toString()} "
// "previousRoute ${previousRoute?.settings?.toString()}");
///iOS侧边手势滑动触发回调 手势开始时回调
}
@override
void didStopUserGesture() {
super.didStopUserGesture();
print("UserNavigatorObserver didStopUserGesture ");
///iOS侧边手势滑动触发停止时回调 不管页面是否退出了都会调用
}
}
返回刷新页面
方法一
@override
void deactivate() {
var bool = ModalRoute.of(context).isCurrent;
if (bool) {
getData();
}
}
方法二
NavigatorUtil.navigatorRouter(context, UpdateNickPage()).then((data) {
......
})
跳转的页面
Navigator.pop(context, params);
点击空白区域 关闭软键盘
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
自定义键盘时禁止软键盘弹出 光标显示
// 设置 readOnly: true,
// 设置 showCursor: true,
Container(
height: 25,
width: 80,
child: TextField(
controller: widget.controller,
focusNode: widget.focusNode,
keyboardType: TextInputType.number,
textAlign: TextAlign.center,
textAlignVertical: TextAlignVertical.center,
inputFormatters: [WhitelistingTextInputFormatter.digitsOnly],
readOnly: true,
showCursor: true,
onTap: () {},
onChanged: widget.onChanged,
style: TextStyle(
color: Colours.fontColor_black,
fontSize: ScreenUtil().setSp(40)),
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4),
borderSide: BorderSide.none),
fillColor: Color.fromRGBO(248, 248, 248, 1),
filled: true,
contentPadding: EdgeInsets.fromLTRB(2, 2, 2, 10),
),
),
),
光标随着输入移动
numberCtrl.selection = TextSelection.fromPosition(TextPosition(offset: numberCtrl.text.length));
InheritedWidget使用场景
当小部件嵌套 而不是所有的小部件都需要传入参数
class MyInheritedWidget extends InheritedWidget {
final Map payPageConfig;
final dynamic pageType;
MyInheritedWidget({
Key key,
@required this.payPageConfig,
@required this.pageType,
@required Widget child,
}) : super(key: key, child: child);
static MyInheritedWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(MyInheritedWidget);
}
@override
bool updateShouldNotify(MyInheritedWidget old) =>
payPageConfig != old.payPageConfig;
}
MyInheritedWidget(
payPageConfig: f,
pageType: f['pageType'],
child: AliPay(),
)
Widget build(BuildContext context) {
final inheritedContext = MyInheritedWidget.of(context);
return Container(
padding: EdgeInsets.only(left: 10, right: 0, top: 5),
child: AliPayQuotaPage(
payConfig: inheritedContext.payPageConfig,
pageType: inheritedContext.pageType,
),
);
}
在滚动容器singleChildScrollView 下使用Expanded
LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraint.maxHeight),
child: IntrinsicHeight(
child: Column(
children: [
Text("Header"),
Expanded(
child: Container(
color: Colors.red,
),
),
Text("Footer"),
],
),
),
),
);
},
)
Dio 请求拦截
使用Interceptor进行Flutter刷新获取新的token
Future getApiClient() async {
dio.interceptors.clear();
dio.interceptors
.add(InterceptorsWrapper(onRequest: (RequestOptions options) {
// Do something before request is sent
options.headers["Authorization"] = "Bearer " + getToken();
return options;
}, onResponse: (Response response) async {
// Do something with response data
if (response.data['code'] == 202) {
dio.interceptors.requestLock.lock();
dio.interceptors.responseLock.lock();
RequestOptions options = response.request;
options.headers["Authorization"] = "Bearer " + await getRefreshToken();
dio.interceptors.requestLock.unlock();
dio.interceptors.responseLock.unlock();
return dio.request(options.path, options: options);
} else {
return response;
}
// return response; // continue
}, onError: (DioError error) async {
return error;
}));
dio.options.baseUrl = baseUrl;
return dio;
}
}
Future getRefreshToken() async {
String refreshToken;
Dio tokenDio = new Dio(); //创建新的Dio实例
tokenDio.options.headers['Authorization'] =
"Bearer " + NetUtil.getToken(); //设置当前的refreshToken
try {
String url = Config.server + '/User/ReplaceToken'; //refreshToken url
var response = await tokenDio.post(url).catchError((onError) {
print(onError);
}); //请求refreshToken刷新的接口
if (response.data['status']) {
refreshToken = response.data['data']; //新的accessToken
SpUtil.putString('token', refreshToken);
}
print('refreshToken===========$refreshToken');
} on DioError catch (e) {}
return refreshToken;
}
多嵌套路由 showDialog 关闭问题
当多个Navigator嵌套在App中时.由showDialog(...)方法创建的对话框路由· 被推送到根导航器.如果应用程序有多个Navigator对象,可能需要调用
Navigator.of(context, rootNavigator: true).pop(result)
来关闭对话框而不仅仅是Navigator.pop(context, result).
如果在2个对话框在1个屏幕上打开时遇到问题,则只需在此Future中放置一个对话框即可
Future.delayed(Duration(seconds: 1), () {
Navigator.of(context, rootNavigator: true).pop(true);
});
检查是否有网络
Future checkInternetState(currentContext, isLoding) async {
try {
String host = "baidu.com"; //判断国内外,谷歌还是百度
final result = await InternetAddress.lookup(host);
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
print("Network is ok.");
return true;
}
} on SocketException catch (_) {
print("Network is error.");
if (isLoding) {
Navigator.pop(currentContext);
}
Util.tip('网络无连接', type: 'warning');
// Navigator.push(currentContext,
// new MaterialPageRoute(builder: (BuildContext context) {
// return NetErrorPage();
// }));
return false;
}
}
获取widget的坐标和大小
RenderBox renderBox = widget.popKey.currentContext.findRenderObject();
//targetWidget的坐标位置
Offset localToGlobal = renderBox.localToGlobal(Offset.zero);
// localToGlobal.dx X坐标
// localToGlobal.dy y坐标
//targetWidget的大小
Size size = renderBox.size; //targetWidget的size
// size.width 宽
// size.height 高
flutter Column 中包含ListView height如果不设置 界面显示会有问题,如果要设置,又不能准确的计算出结果
在外面包一层 Expanded 或者 Flexible 可解决问题
Column(children: [
Text(“测试”),
Expanded(child: Container(
width: ScreenUtil().setWidth(570),
child: ListView.builder(
scrollDirection: Axis.vertical,
itemBuilder:
(context,index){
return _getListItem(index);
},
itemCount: list.length,
),
)),
],)
dart 相关语法
class User {
String name;
void print () {
print('${this.name}');
}
}
?.是什么意思
void foo (User user) {
user?.print()
}
void foo (User user) {
if(user != null) {
user.print();
}
}
?? 是什么意思
var text = user?.name ?? '测试文字'
String text;
if(user != null && user.name != null) {
text = user.name;
}else{
text = '测试文字'
}
??= 是什么意思
User create (User user) {
var user ?? = User() ;
return user;
}
User create (User user) {
if(user == null) {
user = User();
}
return user;
}
_formKey.currentState.reset() 重置表单无效原因
InheritedWidget内部的对象无法更改值
获取状态栏高度
kToolbarHeight
MediaQueryData.fromWindow(window).padding.top
获取软键盘高度
keyBoardHeight = MediaQuery.of(context).viewInsets.bottom;
Flutter SingleChildScrollView代码控制滑动到底部或顶部
通过代码将SingleChildScrollView滑动到底部只需要两个步骤
SingleChildScrollView设置一个ScrollController
ScrollController调用jumpTo控制SingleChildScrollView互动到底部
如下实例代码,点击右下角FloatingActionButton将SingleChildScrollView滑动到底部
scrollController.position.maxScrollExtent获取的是底部的位置
同理,scrollController.position.minScrollExtent可以获取顶部位置
滚动容器的滚动监听
滚动监听及控制
scrollController.position.pixels //当前滚动位置
scrollController.position.maxScrollExtent //最大滚动长度
如何使列表的高度增加到达到定义的最大高度,然后在溢出时显示滚动条
Expanded(
child: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) => _buildItem(_items[index]),
shrinkWrap: true,
)
),
flutter_web 选择文件上传
pickImage() {
final html.InputElement input = html.document.createElement('input');
input
..type = 'file'
..accept = 'image/*';
input.onChange.listen((e) {
if (input.files.isEmpty) return;
final reader = html.FileReader();
reader.readAsDataUrl(input.files[0]);
reader.onError.listen((err) => setState(() {
// error = err.toString();
}));
reader.onLoad.first.then((res) {
final encoded = reader.result as String;
// remove data:image/*;base64 preambule
final stripped =
encoded.replaceFirst(RegExp(r'data:image/[^;]+;base64,'), '');
print(stripped);
setState(() {
// name = input.files[0].name;
// bytes = base64.decode(stripped);
// error = null;
});
});
});
input.click();
}
flutter_inappwebview js通讯
html 代码
JavaScript Handlers (Channels) TEST
flutter 代码
InAppWebView(
initialUrl: widget.url,
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
debuggingEnabled: true,
javaScriptEnabled: true,
useShouldOverrideUrlLoading: true,
useOnLoadResource: true,
cacheEnabled: true,
)),
onWebViewCreated: (InAppWebViewController controller) {
webView = controller;
webView.addJavaScriptHandler(
handlerName: 'handlerFooWithArgs',
callback: (args) {
print(args);
Navigator.of(context).pop("点击了取消");
// it will print: [1, true, [bar, 5], {foo: baz}, {bar: bar_value, baz: baz_value}]
});
},
onLoadStart: (InAppWebViewController controller, String url) {
setState(() {
widget.url = url;
});
},
onLoadStop: (InAppWebViewController controller, String url) async {
String _js = '''
if (!window.flutter_inappwebview.callHandler) {
window.flutter_inappwebview.callHandler = function () {
var _callHandlerID = setTimeout(function () { });
window.flutter_inappwebview._callHandler(arguments[0], _callHandlerID, JSON.stringify(Array.prototype.slice.call(arguments, 1)));
return new Promise(function (resolve, reject) {
window.flutter_inappwebview[_callHandlerID] = resolve;
});
};
}
''';
controller.evaluateJavascript(source: _js);
setState(() {
widget.url = url;
});
},
onProgressChanged:
(InAppWebViewController controller, int progress) {},
),
xcode app 启动强制横屏 打开app后可以横竖屏
info.plist 添加代码
UISupportedInterfaceOrientations~ipad
UIInterfaceOrientationLandscapeRight
AppDelegate 添加代码
override func application(
_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return .allButUpsideDown
}
andriod 打包后apk检测到病毒
因为flavor 分渠道打包引发的问题 在Andriod9 版本之后需要
在build.gradle 的defaultConfig 中加上 flavorDimensions "versionCode"
defaultConfig {
targetSdkVersion:***
minSdkVersion :***
versionCode:***
versionName :***
//版本名后面下面这句话,意思就是flavor dimension它的维度就是该版本号,这样维度就是都是统一的
flavorDimensions "versionCode"
}