國(guó)際化下光標(biāo)無法對(duì)齊
theme: ThemeData(
primaryColor: Style.primaryColor,
accentColor: Style.accentColor,
indicatorColor: Colors.white,
textTheme: TextTheme(
subhead: TextStyle(
textBaseline: TextBaseline.alphabetic) //解決光標(biāo)跟hinText無法對(duì)齊的問題
)
// scaffoldBackgroundColor: Style.bgColor,//全局scaffold背景色
// primarySwatch: Style.primarySwatch,
),
初始化數(shù)據(jù)存儲(chǔ)插件
不進(jìn)行初始化操作 會(huì)在頁面渲染時(shí)無法獲取到存儲(chǔ)的數(shù)據(jù)
void initSp() async {
await SpUtil.getInstance();
if(!mounted) return;
}
dialog在頁面未完成加載前彈出會(huì)報(bào)錯(cuò) 需要在頁面完成加載之后再彈出
WidgetsBinding.instance.addPostFrameCallback((_) {
});
嵌套路由
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
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) {
//取出路由參數(shù)
String msg = ModalRoute.of(context).settings.arguments as String;
… //數(shù)據(jù)處理
}
}
路由監(jiān)聽
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<AppBloc>(context);
}
static List<Route<dynamic>> history = <Route<dynamic>>[];
@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');
//}
///調(diào)用Navigator.of(context).pop() 出棧時(shí)回調(diào)
}
@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====
///調(diào)用Navigator.of(context).push(Route()) 進(jìn)棧時(shí)回調(diào)
}
@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}");
///調(diào)用Navigator.of(context).removeRoute(Route()) 移除某個(gè)路由回調(diào)
}
@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()}");
///調(diào)用Navigator.of(context).replace( oldRoute:Route("old"),newRoute:Route("new)) 替換路由時(shí)回調(diào)
}
@override
void didStartUserGesture(Route route, Route previousRoute) {
super.didStartUserGesture(route, previousRoute);
// print(
// "UserNavigatorObserver didStartUserGesture route ${route.settings.toString()} "
// "previousRoute ${previousRoute?.settings?.toString()}");
///iOS側(cè)邊手勢(shì)滑動(dòng)觸發(fā)回調(diào) 手勢(shì)開始時(shí)回調(diào)
}
@override
void didStopUserGesture() {
super.didStopUserGesture();
print("UserNavigatorObserver didStopUserGesture ");
///iOS側(cè)邊手勢(shì)滑動(dòng)觸發(fā)停止時(shí)回調(diào) 不管頁面是否退出了都會(huì)調(diào)用
}
}
返回刷新頁面
方法一
@override
void deactivate() {
var bool = ModalRoute.of(context).isCurrent;
if (bool) {
getData();
}
}
方法二
NavigatorUtil.navigatorRouter(context, UpdateNickPage()).then((data) {
......
})
跳轉(zhuǎn)的頁面
Navigator.pop(context, params);
點(diǎn)擊空白區(qū)域 關(guān)閉軟鍵盤
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
自定義鍵盤時(shí)禁止軟鍵盤彈出 光標(biāo)顯示
// 設(shè)置 readOnly: true,
// 設(shè)置 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),
),
),
),
光標(biāo)隨著輸入移動(dòng)
numberCtrl.selection = TextSelection.fromPosition(TextPosition(offset: numberCtrl.text.length));
InheritedWidget使用場(chǎng)景
當(dāng)小部件嵌套 而不是所有的小部件都需要傳入?yún)?shù)
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,
),
);
}
在滾動(dòng)容器singleChildScrollView 下使用Expanded
LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraint.maxHeight),
child: IntrinsicHeight(
child: Column(
children: <Widget>[
Text("Header"),
Expanded(
child: Container(
color: Colors.red,
),
),
Text("Footer"),
],
),
),
),
);
},
)
Dio 請(qǐng)求攔截
使用Interceptor進(jìn)行Flutter刷新獲取新的token
Future<Dio> 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<String> getRefreshToken() async {
String refreshToken;
Dio tokenDio = new Dio(); //創(chuàng)建新的Dio實(shí)例
tokenDio.options.headers['Authorization'] =
"Bearer " + NetUtil.getToken(); //設(shè)置當(dāng)前的refreshToken
try {
String url = Config.server + '/User/ReplaceToken'; //refreshToken url
var response = await tokenDio.post(url).catchError((onError) {
print(onError);
}); //請(qǐng)求refreshToken刷新的接口
if (response.data['status']) {
refreshToken = response.data['data']; //新的accessToken
SpUtil.putString('token', refreshToken);
}
print('refreshToken===========$refreshToken');
} on DioError catch (e) {}
return refreshToken;
}
多嵌套路由 showDialog 關(guān)閉問題
當(dāng)多個(gè)Navigator嵌套在App中時(shí).由showDialog(...)方法創(chuàng)建的對(duì)話框路由· 被推送到根導(dǎo)航器.如果應(yīng)用程序有多個(gè)Navigator對(duì)象,可能需要調(diào)用
Navigator.of(context, rootNavigator: true).pop(result)
來關(guān)閉對(duì)話框而不僅僅是Navigator.pop(context, result).
如果在2個(gè)對(duì)話框在1個(gè)屏幕上打開時(shí)遇到問題,則只需在此Future中放置一個(gè)對(duì)話框即可
Future.delayed(Duration(seconds: 1), () {
Navigator.of(context, rootNavigator: true).pop(true);
});
檢查是否有網(wǎng)絡(luò)
Future checkInternetState(currentContext, isLoding) async {
try {
String host = "baidu.com"; //判斷國(guó)內(nèi)外,谷歌還是百度
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('網(wǎng)絡(luò)無連接', type: 'warning');
// Navigator.push(currentContext,
// new MaterialPageRoute(builder: (BuildContext context) {
// return NetErrorPage();
// }));
return false;
}
}
獲取widget的坐標(biāo)和大小
RenderBox renderBox = widget.popKey.currentContext.findRenderObject();
//targetWidget的坐標(biāo)位置
Offset localToGlobal = renderBox.localToGlobal(Offset.zero);
// localToGlobal.dx X坐標(biāo)
// localToGlobal.dy y坐標(biāo)
//targetWidget的大小
Size size = renderBox.size; //targetWidget的size
// size.width 寬
// size.height 高
flutter Column 中包含ListView height如果不設(shè)置 界面顯示會(huì)有問題,如果要設(shè)置,又不能準(zhǔn)確的計(jì)算出結(jié)果
在外面包一層 Expanded 或者 Flexible 可解決問題
Column(children: <Widget>[
Text(“測(cè)試”),
Expanded(child: Container(
width: ScreenUtil().setWidth(570),
child: ListView.builder(
scrollDirection: Axis.vertical,
itemBuilder:
(context,index){
return _getListItem(index);
},
itemCount: list.length,
),
)),
],)
dart 相關(guān)語法
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 ?? '測(cè)試文字'
String text;
if(user != null && user.name != null) {
text = user.name;
}else{
text = '測(cè)試文字'
}
??= 是什么意思
User create (User user) {
var user ?? = User() ;
return user;
}
User create (User user) {
if(user == null) {
user = User();
}
return user;
}
_formKey.currentState.reset() 重置表單無效原因
InheritedWidget內(nèi)部的對(duì)象無法更改值
獲取狀態(tài)欄高度
kToolbarHeight
MediaQueryData.fromWindow(window).padding.top
獲取軟鍵盤高度
keyBoardHeight = MediaQuery.of(context).viewInsets.bottom;
Flutter SingleChildScrollView代碼控制滑動(dòng)到底部或頂部
通過代碼將SingleChildScrollView滑動(dòng)到底部只需要兩個(gè)步驟
SingleChildScrollView設(shè)置一個(gè)ScrollController
ScrollController調(diào)用jumpTo控制SingleChildScrollView互動(dòng)到底部
如下實(shí)例代碼,點(diǎn)擊右下角FloatingActionButton將SingleChildScrollView滑動(dòng)到底部
scrollController.position.maxScrollExtent獲取的是底部的位置
同理,scrollController.position.minScrollExtent可以獲取頂部位置
滾動(dòng)容器的滾動(dòng)監(jiān)聽
scrollController.position.pixels //當(dāng)前滾動(dòng)位置
scrollController.position.maxScrollExtent //最大滾動(dòng)長(zhǎng)度
如何使列表的高度增加到達(dá)到定義的最大高度,然后在溢出時(shí)顯示滾動(dòng)條
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 代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
</head>
<body>
<h1>JavaScript Handlers (Channels) TEST</h1>
<button id="btn"></button>
<script>
window.addEventListener("flutterInAppWebViewPlatformReady", function(event) {
document.getElementById('btn').onclick = function() {
window.flutter_inappwebview
.callHandler('handlerFooWithArgs', 1, true, ['isBack', true]);
}
});
</script>
</body>
</html>
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("點(diǎn)擊了取消");
// 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 啟動(dòng)強(qiáng)制橫屏 打開app后可以橫豎屏
info.plist 添加代碼
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
AppDelegate 添加代碼
override func application(
_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
return .allButUpsideDown
}
andriod 打包后apk檢測(cè)到病毒
因?yàn)閒lavor 分渠道打包引發(fā)的問題 在Andriod9 版本之后需要
在build.gradle 的defaultConfig 中加上 flavorDimensions "versionCode"
defaultConfig {
targetSdkVersion:***
minSdkVersion :***
versionCode:***
versionName :***
//版本名后面下面這句話,意思就是flavor dimension它的維度就是該版本號(hào),這樣維度就是都是統(tǒng)一的
flavorDimensions "versionCode"
}