flutter 開發(fā)過程中遇到的問題總結(jié)

國(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)聽

滾動(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"
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容