#android #ios #flutter #flutter-layout
#Android #iOS #флаттер #флаттер-макет
Вопрос:
Я разрабатываю приложение flutter, ниже приведен мой код пользовательского интерфейса
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height),
child: Container(child: _LoginUI(),),)
),
);
}
}
class _LoginUI extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _LoginState();
}
}
class _LoginState extends State<_LoginUI> {
final _formKey = GlobalKey<FormState>();
//FIXME When validate error occures, the fields get super small
TextEditingController _phoneNumber = TextEditingController();
TextEditingController _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Container(
height: double.infinity,
width: double.infinity,
child:
Stack(
fit: StackFit.loose,
children: <Widget>[
SafeArea(
child: Container(
margin: EdgeInsets.only(top: 25),
child: Image.asset("assets/images/login_image.png"),
),
),
Positioned(
top: 275,
child: Container(
height: 600,
width: MediaQuery.of(context).size.width,
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.only(
topLeft: const Radius.circular(40.0),
topRight: const Radius.circular(40.0))),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
margin:
EdgeInsets.only(top: 20, left: 10, right: 10),
child: Image.asset(
"assets/images/logo.png",
width: 200,
height: 50),
),
)
],
),
Form(
key: _formKey,
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(
top: 40,
),
child: SizedBox(
width: MediaQuery.of(context).size.width * .90,
height: 36,
child: TextFormField(
controller: _phoneNumber,
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
contentPadding: const EdgeInsets.only(
top: 2, bottom: 2, left: 8),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
),
hintText: "Email",
),
),
)),
Container(
margin: EdgeInsets.only(
top: 15,
),
child: SizedBox(
height: 36,
width: MediaQuery.of(context).size.width * .90,
child: TextFormField(
controller: _passwordController,
keyboardType: TextInputType.visiblePassword,
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
contentPadding: const EdgeInsets.only(
top: 2, bottom: 2, left: 8),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(30.0),
),
hintText: "Password",
),
),
),
),
Align(
alignment: Alignment.bottomRight,
child: Container(
margin: EdgeInsets.only(
top: 1, left: 10, right: 10),
child: FlatButton(
onPressed: () {
Navigator.pushNamed(context, "/password-reset");
},
child: Text("Forgot Password?",
style: TextStyle(
fontFamily: 'Roboto-Medium',
fontSize: 14.0,
letterSpacing: 1.25,
color:
Color.fromRGBO(75, 56, 137, 80))),
)),
),
Container(
margin:
EdgeInsets.only(top: 1, left: 10, right: 10),
child: SizedBox(
width: MediaQuery.of(context).size.width * .90,
child: RaisedButton(
color: Color.fromRGBO(75, 56, 137, 80),
textColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius:
new BorderRadius.circular(18.0),
side: BorderSide(
color:
Color.fromRGBO(75, 56, 137, 80))),
child: Text(
"LOGIN",
style: Theme.of(context).textTheme.button,
),
onPressed: () {
String email = _phoneNumber.text;
String password = _passwordController.text;
if (_formKey.currentState.validate()) {
loginProcess(email, password);
}
},
),
))
],
),
),
Container(
margin: EdgeInsets.only(top: 1, left: 10, right: 10),
child: FlatButton(
onPressed: () {
Navigator.pushNamed(context, "/register");
},
child: RichText(
text: TextSpan(children: <TextSpan>[
TextSpan(
text: "Not a member yet? ",
style: TextStyle(fontFamily: 'Roboto-Regular', fontSize: 14.0, letterSpacing: 0.25, color:Color.fromRGBO(75, 56, 137, 80 )),
),
TextSpan(
text: "Create an Account",
style: TextStyle(decoration: TextDecoration.underline, fontFamily: 'Roboto-Regular', fontSize: 14.0, letterSpacing: 0.25, color:Color.fromRGBO(75, 56, 137, 80 ),
),)
]),
),
)),
],
),
),
),
],
)
//child: Image.asset("assets/images/login_image.png"),
);
}
На рисунке ниже показан пользовательский интерфейс, который я получаю на большинстве телефонов, который является правильным, и пользовательский интерфейс, который я получаю на некоторых телефонах, который является неточным.
Как вы можете видеть, в неточной версии Боттон внизу отсутствует. Особенно это происходит в
Sony Xperia
серии, где размер экрана составляет 4,6 дюйма и разрешение 720×1280.
Самый простой способ решить эту проблему — изменить значение внутри positioned
на 250, что приведет к увеличению всего блока под изображением. Но тогда это некрасиво в некоторых телефонах, потому что закрывает верхнее изображение. У меня есть другой экран, который имеет ту же проблему, что и этот, на нем больше полей. Таким образом, такое решение, как установка positioned
значения во что-то другое, не будет работать.
Каково наилучшее решение, чтобы убедиться, что весь экран виден на всех телефонах?
Ответ №1:
Мой метод был бы следующим:
Затем оберните Scaffold
с SafeArea
(это необязательно)
Если виджет правильно расположен на текущем устройстве, например, на текущем эмуляторе, который имеет разрешение 480x800
. И виджет позиционируется 250px
(пример из вашего варианта использования)
тогда оно расположено примерно на 31,25% от верхнего ((250/800) * 100).
Тогда я бы использовал MediaQuery.of(Context).size.height*0.3125
для позиционирования виджета сверху.
Теперь, независимо от высоты телефона, виджет всегда будет располагаться на 31,25% сверху.
Аналогично вы можете сделать и для left
позиции/
Мое предложение также заключалось бы в том, чтобы сделать то же самое с шириной и высотой виджета, чтобы размер виджета также был таким же, как на экране устройства. и не выглядит слишком большим или маленьким для некоторых устройств. Не для каждого виджета, но для некоторых.
Ответ №2:
Я изменил ваш код, но у меня нет файлов ресурсов, поэтому вместо этого я изменил SizedBox.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: _LoginUI(),
);
}
}
class _LoginUI extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _LoginState();
}
}
class _LoginState extends State<_LoginUI> {
final _formKey = GlobalKey<FormState>();
//FIXME When validate error occures, the fields get super small
TextEditingController _phoneNumber = TextEditingController();
TextEditingController _passwordController = TextEditingController();
@override
Widget build(BuildContext context) {
return Container(
child: SafeArea(
child: LayoutBuilder(builder:
(BuildContext context, BoxConstraints viewportConstraints) {
return SingleChildScrollView(
reverse: true,
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(top: 25),
// child: Image.asset("assets/images/login_image.png"),
child: SizedBox(
height: 200,
child: Text('Login_image'),
),
),
Container(
width: MediaQuery.of(context).size.width,
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.only(
topLeft: const Radius.circular(40.0),
topRight: const Radius.circular(40.0))),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
margin: EdgeInsets.only(
top: 20, left: 10, right: 10),
child: Container(
height: 50,
child: Text('LOGO'),
),
//Image.asset("assets/images/logo.png",
// width: 200, height: 50),
),
)
],
),
Form(
key: _formKey,
child: Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(
top: 40,
),
child: SizedBox(
width:
MediaQuery.of(context).size.width * .90,
height: 36,
child: TextFormField(
controller: _phoneNumber,
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
contentPadding: const EdgeInsets.only(
top: 2, bottom: 2, left: 8),
border: OutlineInputBorder(
borderRadius:
BorderRadius.circular(30.0),
),
hintText: "Email",
),
),
)),
Container(
margin: EdgeInsets.only(
top: 15,
),
child: SizedBox(
height: 36,
width:
MediaQuery.of(context).size.width * .90,
child: TextFormField(
controller: _passwordController,
keyboardType: TextInputType.visiblePassword,
validator: (value) {
if (value.isEmpty) {
return 'Please enter some text';
}
return null;
},
decoration: InputDecoration(
filled: true,
fillColor: Colors.white,
contentPadding: const EdgeInsets.only(
top: 2, bottom: 2, left: 8),
border: OutlineInputBorder(
borderRadius:
BorderRadius.circular(30.0),
),
hintText: "Password",
),
),
),
),
Align(
alignment: Alignment.bottomRight,
child: Container(
margin: EdgeInsets.only(
top: 1, left: 10, right: 10),
child: FlatButton(
onPressed: () {
Navigator.pushNamed(
context, "/password-reset");
},
child: Text("Forgot Password?",
style: TextStyle(
fontFamily: 'Roboto-Medium',
fontSize: 14.0,
letterSpacing: 1.25,
color: Color.fromRGBO(
75, 56, 137, 80))),
)),
),
Container(
margin: EdgeInsets.only(
top: 1, left: 10, right: 10),
child: SizedBox(
width:
MediaQuery.of(context).size.width * .90,
child: RaisedButton(
color: Color.fromRGBO(75, 56, 137, 80),
textColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius:
new BorderRadius.circular(18.0),
side: BorderSide(
color: Color.fromRGBO(
75, 56, 137, 80))),
child: Text(
"LOGIN",
style:
Theme.of(context).textTheme.button,
),
onPressed: () {
String email = _phoneNumber.text;
String password =
_passwordController.text;
if (_formKey.currentState.validate()) {
// loginProcess(email, password);
}
},
),
))
],
),
),
Container(
margin:
EdgeInsets.only(top: 1, left: 10, right: 10),
child: FlatButton(
onPressed: () {
Navigator.pushNamed(context, "/register");
},
child: RichText(
text: TextSpan(children: <TextSpan>[
TextSpan(
text: "Not a member yet? ",
style: TextStyle(
fontFamily: 'Roboto-Regular',
fontSize: 14.0,
letterSpacing: 0.25,
color: Color.fromRGBO(75, 56, 137, 80)),
),
TextSpan(
text: "Create an Account",
style: TextStyle(
decoration: TextDecoration.underline,
fontFamily: 'Roboto-Regular',
fontSize: 14.0,
letterSpacing: 0.25,
color: Color.fromRGBO(75, 56, 137, 80),
),
)
]),
),
)),
SizedBox(height: 400),
],
),
),
],
),
),
);
}),
),
);
//child: Image.asset("assets/images/login_image.png"),
}
}