Flutter 使用TextEdit和DirectionBuilder解决iPad问题
我有两个文本编辑字段的登录屏幕。 在纵向模式下,有1列包含所有小部件,在横向模式下有2列(左侧为徽标,右侧为其余小部件) 我只在iPad Pro 4 gen iOS 14(设备和模拟器)上体验到在肖像模式下的奇怪行为 当TextEdit get focus键盘开始显示(底部可见的几个像素)时,下一个横向视图(在设备上以纵向方向)显示第二个和下一个屏幕显示纵向视图(无键盘) 在Android和iPhone XS和5s设备上没有问题Flutter 使用TextEdit和DirectionBuilder解决iPad问题,flutter,ipad,orientation,textedit,Flutter,Ipad,Orientation,Textedit,我有两个文本编辑字段的登录屏幕。 在纵向模式下,有1列包含所有小部件,在横向模式下有2列(左侧为徽标,右侧为其余小部件) 我只在iPad Pro 4 gen iOS 14(设备和模拟器)上体验到在肖像模式下的奇怪行为 当TextEdit get focus键盘开始显示(底部可见的几个像素)时,下一个横向视图(在设备上以纵向方向)显示第二个和下一个屏幕显示纵向视图(无键盘) 在Android和iPhone XS和5s设备上没有问题 import 'dart:convert'; import 'p
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:moja_plytoteka/API/api.dart';
import 'package:moja_plytoteka/AppUtils/globals.dart';
import 'package:moja_plytoteka/AppUtils/gradientButton.dart';
import 'package:moja_plytoteka/AppUtils/sharedPreferences.dart';
import 'package:rflutter_alert/rflutter_alert.dart';
import 'package:email_validator/email_validator.dart';
class LoginScreen extends StatefulWidget {
LoginScreen({Key key}) : super(key: key);
@override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final inputEmailController = TextEditingController();
final inputPasswordController = TextEditingController();
final myHintStyle = TextStyle(fontSize: 14.0, color: Colors.grey);
final myTextStyle = TextStyle(fontSize: 14.0, color: Colors.black);
final myAlertTextStyle = AlertStyle(descStyle: TextStyle(fontSize: 14.0, color: Colors.black, fontWeight: FontWeight.w400));
OutlineInputBorder inputBorder = OutlineInputBorder(
borderSide: const BorderSide(color: appColorPink_2, width: 1.5),
borderRadius: BorderRadius.circular(inputRadius),
);
@override
void initState() {
_getName();
inputEmailController.addListener(_nameChanged);
super.initState();
}
@override
void dispose() {
inputEmailController.dispose();
inputPasswordController.dispose();
super.dispose();
}
void _getName() async {
inputEmailController.text = await SharedPreferencesSF.getName();
}
void _nameChanged(){
SharedPreferencesSF.setName(inputEmailController.text);
}
bool isLandscape() {
return MediaQuery.of(context).orientation == Orientation.landscape ? true : false;
}
bool isTablet() {
return MediaQuery.of(context).size.height > 1000.0 ? true : false;
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
backgroundColor: Colors.white,
body: OrientationBuilder(
builder: (context, orientation) {
return GestureDetector(
onTap: () {FocusScope.of(context).requestFocus(FocusNode());},
child: Center(
child: orientation == Orientation.portrait
? _portraitView()
: _landscapeView()
),
);
}
),
),
);
}
Widget _portraitView(){
return SafeArea(
child: Container(
constraints: BoxConstraints(maxWidth: 600),
child: Center(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.only(left: 24.0, right: 24.0),
children: <Widget>[
SizedBox(height: 20.0),
Container(child: nameText()),
SizedBox(height: isTablet() ? 50.0 : 15.0),
logo(),
SizedBox(height: isTablet() ? 70.0 : 40.0),
loginText(),
SizedBox(height: isTablet() ? 20.0 : 0.0),
email(),
SizedBox(height: 15.0),
password(),
SizedBox(height: isTablet() ? 70.0 : 20.0),
loginButton(),
SizedBox(height: 40.0),
],
),
),
),
);
}
Widget _landscapeView(){
// // Return Your Widget View Here Which you want to Load on Landscape Orientation.
return Row(
children: [
Expanded(
flex: 1,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
logo(),
SizedBox(height: 15.0),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 30.0),
child: nameText(),
),
],
),
),
),
Expanded(
flex: 1,
child: Stack(
children: [
Container(
color: appColorPink_2,
),
Center(
child: SingleChildScrollView(
child: Container(
//color: appColorPink_2,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: MediaQuery.of(context).size.height<321 ? 15.0 : 50.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
loginText(),
email(),
SizedBox(height: 15.0),
password(),
SizedBox(height: 15.0),
loginButton(),
],
),
),
),
),
),
],
),
),
]
);
}
_loginProcess(context) async{
bool loginDataOK = await _checkLoginData(context);
if(!loginDataOK) return;
_showLoaderDialog(context);
LoginResult loginResult = await API.loginCheck(inputEmailController.text, passwordEncoded, deviceid, appToken, context);
Navigator.pop(context);
// Login symulation
var result = await Navigator.of(context).pushNamed('/testAPILogin');
loginResult = result as LoginResult;
print('Login result: $loginResult');
switch (loginResult) {
case LoginResult.permitted:
Navigator.of(context).pushNamed('/dashboardScreen');
break;
case LoginResult.rejected: {
Alert(
context: context,
type: AlertType.error,
title: "BŁĄD LOGOWANIA",
desc: "Niepoprawne dane logowania. Spróbuj ponownie.",
style: myAlertTextStyle,
buttons: [
DialogButton(
child: Text(
"OK",
style: TextStyle(color: Colors.white, fontSize: 20),
),
color: appColorPink_2,
onPressed: () => Navigator.pop(context),
width: 120,
)
],
).show();
return ;
}
break;
case LoginResult.tooManyDevices: {
Alert(
context: context,
type: AlertType.warning,
title: "NIEDOZWOLONE LOGOWANIE",
desc: "Dla swojego konta masz zarejestrowane za dużo urządzeń. Usuń inne urządzenie żeby zalogować się na tym.",
style: myAlertTextStyle,
buttons: [
DialogButton(
child: Text(
"OK",
style: TextStyle(color: Colors.white, fontSize: 20),
),
color: appColorPink_2,
onPressed: () => Navigator.pop(context),
width: 120,
)
],
).show();
return ;
}
break;
default: {
Alert(
context: context,
type: AlertType.error,
title: "NIEZNANY BŁĄD",
desc: "Spróbuj zalogować się ponownie.",
style: myAlertTextStyle,
buttons: [
DialogButton(
child: Text(
"OK",
style: TextStyle(color: Colors.white, fontSize: 20),
),
color: appColorPink_2,
onPressed: () => Navigator.pop(context),
width: 120,
)
],
).show();
return ;
}
}
}
Future _checkLoginData(context) async {
// Check e-mail
final bool isValid = EmailValidator.validate(inputEmailController.text);
if (!isValid) {
Alert(
context: context,
type: AlertType.error,
title: "BŁĄD",
desc: "Wprowadź poprawny adres e-mail.",
style: myAlertTextStyle,
buttons: [
DialogButton(
child: Text(
"OK",
style: TextStyle(color: Colors.white, fontSize: 20),
),
color: appColorPink_2,
onPressed: () => Navigator.pop(context),
width: 120,
)
],
).show();
return false;
}
// Check password
if (inputPasswordController.text.length==0) {
Alert(
context: context,
type: AlertType.error,
title: "BŁĄD",
desc: "Wprowadź hasło.",
style: myAlertTextStyle,
buttons: [
DialogButton(
child: Text(
"OK",
style: TextStyle(color: Colors.white, fontSize: 20),
),
color: appColorPink_2,
onPressed: () => Navigator.pop(context),
width: 120,
)
],
).show();
return false;
}
// MD5 encode
var bytes = utf8.encode(inputPasswordController.text);
passwordEncoded = md5.convert(bytes).toString();
return true;
}
_showLoaderDialog(BuildContext context){
AlertDialog alert=AlertDialog(
//backgroundColor: appColorPink_2,
content: Row(
children: [
CircularProgressIndicator(
backgroundColor: Colors.white,
valueColor: AlwaysStoppedAnimation<Color>(appColorPink_1),
strokeWidth: 3.0,
),
SizedBox(width: 10.0),
Container(margin: EdgeInsets.only(left: 7),child:Text("Logowanie..." , style: TextStyle(color: appColorPink_2, fontSize: 18.0, fontWeight: FontWeight.w500),)),
],),
);
showDialog(barrierDismissible: false,
context:context,
builder:(BuildContext context){
return WillPopScope(
onWillPop: () async => false,
child: alert
);
},
);
}
nameText() {
return Padding(
padding: const EdgeInsets.all(12.0),
child: Image.asset('assets/images/napis_02.png'),
);
}
logo() {
return CircleAvatar(
backgroundColor: appColorPink_2,
radius: 50.0,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Image.asset('assets/images/logo_white.png'),
),
);
}
loginText() {
return Padding(
padding: const EdgeInsets.fromLTRB(0, 0, 0, 15),
child: Center(child: Text('Logowanie do aplikacji', style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold, color: isLandscape() ? Colors.white : Colors.black), )),
);
}
email() {
return TextField(
controller: inputEmailController,
keyboardType: TextInputType.emailAddress,
autofocus: false,
//initialValue: 'alucard@gmail.com',
decoration: InputDecoration(
filled: isLandscape(),
fillColor: Colors.white,
hintText: 'Wprowadź e-mail',
hintStyle: myHintStyle,
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(inputRadius)),
focusedBorder: inputBorder,
isDense: true,
),
style: myTextStyle,
);
}
password() {
return TextField(
controller: inputPasswordController,
autofocus: false,
//initialValue: 'some password',
obscureText: true,
decoration: InputDecoration(
filled: isLandscape(),
fillColor: Colors.white,
hintText: 'Wprowadź hasło',
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(inputRadius),),
hintStyle: myHintStyle,
focusedBorder: inputBorder,
isDense: true,
),
style: myTextStyle,
);
}
loginButton() {
return RaisedGradientButton(
width: double.infinity,
textPadding: isTablet() ? 20.0 : 15.0,
text: 'Zaloguj',
isBorder: isLandscape() ? true : false,
borderColor: Colors.white,
gradient: LinearGradient(
colors: <Color>[appColorPink_1, appColorPink_2],
),
onPressed: (){
FocusScope.of(context).requestFocus(FocusNode());
_loginProcess(context);
}
);
}
}
导入'dart:convert';
导入“包:crypto/crypto.dart”;
进口“包装:颤振/材料.省道”;
进口“包装:moja_plytoteka/API/API.dart”;
进口“包装:moja_plytoteka/AppUtils/globals.dart”;
进口“包装:moja_plytoteka/AppUtils/gradientButton.dart”;
导入“包装:moja_plytoteka/AppUtils/SharedReferences.dart”;
导入“package:rflutter_alert/rflutter_alert.dart”;
导入“包:email_validator/email_validator.dart”;
类LoginScreen扩展StatefulWidget{
LoginScreen({Key}):超级(Key:Key);
@凌驾
_LoginsScreenState createState()=>\u LoginsScreenState();
}
类_LoginScreenState扩展状态{
最终输入EmailController=TextEditingController();
final inputPasswordController=TextEditingController();
最终myHintStyle=TextStyle(fontSize:14.0,颜色:Colors.grey);
最终myTextStyle=TextStyle(fontSize:14.0,颜色:Colors.black);
最终myAlertTextStyle=AlertStyle(descStyle:TextStyle(fontSize:14.0,颜色:Colors.black,fontWeight:fontWeight.w400));
OutlineInputBorder inputBorder=OutlineInputBorder(
borderSide:const borderSide(颜色:appColorPink_2,宽度:1.5),
边界半径:边界半径。圆形(inputRadius),
);
@凌驾
void initState(){
_getName();
inputEmailController.addListener(\u name已更改);
super.initState();
}
@凌驾
无效处置(){
inputEmailController.dispose();
inputPasswordController.dispose();
super.dispose();
}
void\u getName()异步{
inputEmailController.text=等待SharedPreferencesf.getName();
}
void _nameChanged(){
SharedPreferencesf.setName(inputEmailController.text);
}
布尔岛景观{
返回MediaQuery.of(context.orientation==orientation.scape?true:false;
}
bool-isTablet(){
返回MediaQuery.of(context).size.height>1000.0?true:false;
}
@凌驾
小部件构建(构建上下文){
返回式示波器(
onWillPop:()async=>false,
孩子:脚手架(
背景颜色:Colors.white,
body:OrientationBuilder(
生成器:(上下文、方向){
返回手势检测器(
onTap:({FocusScope.of(context).requestFocus(FocusNode());},
儿童:中心(
子对象:方向==方向。纵向
()
:_landscapeView()
),
);
}
),
),
);
}
Widget_/view(){
返回安全区(
子:容器(
约束:框约束(最大宽度:600),
儿童:中心(
子:ListView(
收缩膜:对,
填充:仅限边设置(左:24.0,右:24.0),
儿童:[
尺寸箱(高度:20.0),
容器(子项:nameText()),
SizedBox(高度:iTablet()?50.0:15.0),
logo(),
SizedBox(高度:iTablet()?70.0:40.0),
loginText(),
SizedBox(高度:iTablet()?20.0:0.0),
电子邮件(),
尺寸箱(高度:15.0),
密码(),
SizedBox(高度:iTablet()?70.0:20.0),
登录按钮(),
尺寸箱(高度:40.0),
],
),
),
),
);
}
小部件_landscapeView(){
////在此处返回要在横向加载的小部件视图。
返回行(
儿童:[
扩大(
弹性:1,
子:SingleChildScrollView(
子:列(
mainAxisAlignment:mainAxisAlignment.center,
儿童:[
logo(),
尺寸箱(高度:15.0),
填充物(
填充:常量边集。对称(水平:30.0),
子项:nameText(),
),
],
),
),
),
扩大(
弹性:1,
子:堆栈(
儿童:[
容器(
颜色:appColorPink_2,
),
居中(
子:SingleChildScrollView(
子:容器(
//颜色:appColorPink_2,
孩子:填充(
padding:EdgeInsets.symmetric(水平):MediaQuery.of(上下文).size.height Navigator.pop(上下文),
宽度:120,
)
],
).show();
返回;
}
打破
案例登录Result.tooManyDevices:{
警觉的(
上下文:上下文,
类型:AlertType.warning,
标题:“NIEDOZWOLONE LOGOWANIE”,
描述:“斯沃耶戈·孔塔·马斯茨·扎雷杰斯特罗瓦内·扎洛戈瓦奇·西西纳·季姆·乌尔兹涅·乌尔兹涅·乌尔兹涅。”,
样式:myAlertTextStyle,
按钮:[
对话框按钮(
子:文本(
"