flutter 开发过程中遇到的问题总结

国际化下光标无法对齐

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"
}

你可能感兴趣的:(flutter 开发过程中遇到的问题总结)