Как работать с индикатором прогресса в флаттере?
Я новичок в трепете и хотел узнать, что лучше добавить CircularProgressIndicator
в мой макет. Например, мой логин. Этот вид имеет имя пользователя, пароль и логин. Мне нужно создать оверлейный макет (с Opacity
), который при загрузке покажет индикатор прогресса, как я использую в NativeScript, но я немного смущен тем, как это сделать, и тоже, если это лучший способ. Например, в NativeScript я добавляю IndicatorActivity в основной макет и устанавливаю занятый в true или false, поэтому он накладывает все компоненты просмотра при загрузке.
Редактировать:
Я смог достичь этого результата:
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _loading = false;
void _onLoading() {
setState(() {
_loading = true;
new Future.delayed(new Duration(seconds: 3), _login);
});
}
Future _login() async{
setState((){
_loading = false;
});
}
@override
Widget build(BuildContext context) {
var body = new Column(
children: <Widget>[
new Container(
height: 40.0,
padding: const EdgeInsets.all(10.0),
margin: const EdgeInsets.fromLTRB(15.0, 150.0, 15.0, 0.0),
decoration: new BoxDecoration(
color: Colors.white,
),
child: new TextField(
decoration: new InputDecoration.collapsed(hintText: "username"),
),
),
new Container(
height: 40.0,
padding: const EdgeInsets.all(10.0),
margin: const EdgeInsets.all(15.0),
decoration: new BoxDecoration(
color: Colors.white,
),
child: new TextField(
decoration: new InputDecoration.collapsed(hintText: "password"),
),
),
],
);
var bodyProgress = new Container(
child: new Stack(
children: <Widget>[
body,
new Container(
alignment: AlignmentDirectional.center,
decoration: new BoxDecoration(
color: Colors.white70,
),
child: new Container(
decoration: new BoxDecoration(
color: Colors.blue[200],
borderRadius: new BorderRadius.circular(10.0)
),
width: 300.0,
height: 200.0,
alignment: AlignmentDirectional.center,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Center(
child: new SizedBox(
height: 50.0,
width: 50.0,
child: new CircularProgressIndicator(
value: null,
strokeWidth: 7.0,
),
),
),
new Container(
margin: const EdgeInsets.only(top: 25.0),
child: new Center(
child: new Text(
"loading.. wait...",
style: new TextStyle(
color: Colors.white
),
),
),
),
],
),
),
),
],
),
);
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Container(
decoration: new BoxDecoration(
color: Colors.blue[200]
),
child: _loading ? bodyProgress : body
),
floatingActionButton: new FloatingActionButton(
onPressed: _onLoading,
tooltip: 'Loading',
child: new Icon(Icons.check),
),
);
}
}
Я все еще приспосабливаюсь к идее государств. Этот код находится в ожидании при работе с флаттером?
Спасибо!
Ответы
Ответ 1
Во флаттере есть несколько способов справиться с асинхронными действиями.
Ленивый способ сделать это может быть использование модального. Который будет блокировать ввод пользователя, тем самым предотвращая любые нежелательные действия.
Это потребует очень мало изменений в вашем коде. Просто измените ваш _onLoading
на что-то вроде этого:
void _onLoading() {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return Dialog(
child: new Row(
mainAxisSize: MainAxisSize.min,
children: [
new CircularProgressIndicator(),
new Text("Loading"),
],
),
);
},
);
new Future.delayed(new Duration(seconds: 3), () {
Navigator.pop(context); //pop dialog
_login();
});
}
Самый идеальный способ сделать это - использовать FutureBuilder
и виджет с сохранением состояния. С чего ты начал.
Хитрость заключается в том, что вместо наличия boolean loading = false
в вашем штате, вы можете напрямую использовать Future<MyUser> user
А затем передайте его в качестве аргумента FutureBuilder
, который даст вам некоторую информацию, такую как "hasData" или экземпляр MyUser
, когда закончите.
Это привело бы к чему-то вроде этого:
@immutable
class MyUser {
final String name;
MyUser(this.name);
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<MyUser> user;
void _logIn() {
setState(() {
user = new Future.delayed(const Duration(seconds: 3), () {
return new MyUser("Toto");
});
});
}
Widget _buildForm(AsyncSnapshot<MyUser> snapshot) {
var floatBtn = new RaisedButton(
onPressed:
snapshot.connectionState == ConnectionState.none ? _logIn : null,
child: new Icon(Icons.save),
);
var action =
snapshot.connectionState != ConnectionState.none && !snapshot.hasData
? new Stack(
alignment: FractionalOffset.center,
children: <Widget>[
floatBtn,
new CircularProgressIndicator(
backgroundColor: Colors.red,
),
],
)
: floatBtn;
return new ListView(
padding: const EdgeInsets.all(15.0),
children: <Widget>[
new ListTile(
title: new TextField(),
),
new ListTile(
title: new TextField(obscureText: true),
),
new Center(child: action)
],
);
}
@override
Widget build(BuildContext context) {
return new FutureBuilder(
future: user,
builder: (context, AsyncSnapshot<MyUser> snapshot) {
if (snapshot.hasData) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Hello ${snapshot.data.name}"),
),
);
} else {
return new Scaffold(
appBar: new AppBar(
title: new Text("Connection"),
),
body: _buildForm(snapshot),
);
}
},
);
}
}
Ответ 2
Для меня один аккуратный способ сделать это - показать SnackBar
внизу, в то время как процесс Signing-In имеет место, это пример того, что я имею в виду:
Вот как настроить SnackBar
.
Определите глобальный ключ для вашего Scaffold
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Добавьте его в свой атрибут key
Scaffold
return new Scaffold(
key: _scaffoldKey,
.......
Моя кнопка onPressed
callback:
onPressed: () {
_scaffoldKey.currentState.showSnackBar(
new SnackBar(duration: new Duration(seconds: 4), content:
new Row(
children: <Widget>[
new CircularProgressIndicator(),
new Text(" Signing-In...")
],
),
));
_handleSignIn()
.whenComplete(() =>
Navigator.of(context).pushNamed("/Home")
);
}
Это действительно зависит от того, как вы хотите создать свой макет, и я не уверен, что вы имеете в виду.
редактировать
Вероятно, вы так захотите, я использовал Stack для достижения этого результата и просто показываю или скрываю свой индикатор на основе onPressed
class TestSignInView extends StatefulWidget {
@override
_TestSignInViewState createState() => new _TestSignInViewState();
}
class _TestSignInViewState extends State<TestSignInView> {
bool _load = false;
@override
Widget build(BuildContext context) {
Widget loadingIndicator =_load? new Container(
color: Colors.grey[300],
width: 70.0,
height: 70.0,
child: new Padding(padding: const EdgeInsets.all(5.0),child: new Center(child: new CircularProgressIndicator())),
):new Container();
return new Scaffold(
backgroundColor: Colors.white,
body: new Stack(children: <Widget>[new Padding(
padding: const EdgeInsets.symmetric(vertical: 50.0, horizontal: 20.0),
child: new ListView(
children: <Widget>[
new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center
,children: <Widget>[
new TextField(),
new TextField(),
new FlatButton(color:Colors.blue,child: new Text('Sign In'),
onPressed: () {
setState((){
_load=true;
});
//Navigator.of(context).push(new MaterialPageRoute(builder: (_)=>new HomeTest()));
}
),
],),],
),),
new Align(child: loadingIndicator,alignment: FractionalOffset.center,),
],));
}
}
Ответ 3
1. Без плагина
class IndiSampleState extends State<ProgHudPage> {
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Demo'),
),
body: Center(
child: RaisedButton(
color: Colors.blueAccent,
child: Text('Login'),
onPressed: () async {
showDialog(
context: context,
builder: (BuildContext context) {
return Center(child: CircularProgressIndicator(),);
});
await loginAction();
Navigator.pop(context);
},
),
));
}
Future<bool> loginAction() async {
//replace the below line of code with your login request
await new Future.delayed(const Duration(seconds: 2));
return true;
}
}
2. С плагином
проверьте этот плагин progress_hud
добавить зависимость в файл pubspec.yaml
dev_dependencies:
progress_hud:
импортировать пакет
import 'package:progress_hud/progress_hud.dart';
Пример кода приведен ниже, чтобы показать и скрыть индикатор
class ProgHudPage extends StatefulWidget {
@override
_ProgHudPageState createState() => _ProgHudPageState();
}
class _ProgHudPageState extends State<ProgHudPage> {
ProgressHUD _progressHUD;
@override
void initState() {
_progressHUD = new ProgressHUD(
backgroundColor: Colors.black12,
color: Colors.white,
containerColor: Colors.blue,
borderRadius: 5.0,
loading: false,
text: 'Loading...',
);
super.initState();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('ProgressHUD Demo'),
),
body: new Stack(
children: <Widget>[
_progressHUD,
new Positioned(
child: RaisedButton(
color: Colors.blueAccent,
child: Text('Login'),
onPressed: () async{
_progressHUD.state.show();
await loginAction();
_progressHUD.state.dismiss();
},
),
bottom: 30.0,
right: 10.0)
],
));
}
Future<bool> loginAction()async{
//replace the below line of code with your login request
await new Future.delayed(const Duration(seconds: 2));
return true;
}
}
Ответ 4
Создайте bool isLoading
и установите для него false
. С помощью троичного оператора, когда пользователь нажимает на кнопку входа в систему, установите состояние isLoading
в true
. Вместо кнопки входа в систему вы получите индикатор круговой загрузки
isLoading ? new PrimaryButton(
key: new Key('login'),
text: 'Login',
height: 44.0,
onPressed: setState((){isLoading = true;}))
: Center(
child: CircularProgressIndicator(),
),
Вы можете увидеть скриншоты, как это выглядит, пока пользователь не нажал на кнопку входа
После нажатия на логин
В то же время вы можете запустить процесс входа в систему и войти в систему пользователя. Если учетные данные пользователя неверны, то вы снова перейдете с setState
от isLoading
до false
, так что индикатор загрузки станет невидимым, а кнопка входа станет видимой для пользователя.
Кстати, primaryButton, используемый в коде, является моей пользовательской кнопкой. Вы можете сделать то же самое с OnPressed
в button
.
Ответ 5
Я выбрал следующий подход, который использует простой виджет индикатора модального прогресса, который оборачивает все, что вы хотите сделать модальным во время асинхронного вызова.
В примере из пакета также рассматривается, как обрабатывать проверку формы при выполнении асинхронных вызовов для проверки формы (подробности этой проблемы см. в flutter/Issues/9688). Например, не покидая форму, этот метод проверки асинхронной формы можно использовать для проверки нового имени пользователя по существующим именам в базе данных при регистрации.
https://pub.dartlang.org/packages/modal_progress_hud
Вот демонстрация примера, поставляемого с пакетом (с исходным кодом):
Пример может быть адаптирован к другому поведению модального индикатора прогресса (например, различные анимации, дополнительный текст в модальном режиме и т.д.).
Ответ 6
Это мое решение со стеклом
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:async';
final themeColor = new Color(0xfff5a623);
final primaryColor = new Color(0xff203152);
final greyColor = new Color(0xffaeaeae);
final greyColor2 = new Color(0xffE8E8E8);
class LoadindScreen extends StatefulWidget {
LoadindScreen({Key key, this.title}) : super(key: key);
final String title;
@override
LoginScreenState createState() => new LoginScreenState();
}
class LoginScreenState extends State<LoadindScreen> {
SharedPreferences prefs;
bool isLoading = false;
Future<Null> handleSignIn() async {
setState(() {
isLoading = true;
});
prefs = await SharedPreferences.getInstance();
var isLoadingFuture = Future.delayed(const Duration(seconds: 3), () {
return false;
});
isLoadingFuture.then((response) {
setState(() {
isLoading = response;
});
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
widget.title,
style: TextStyle(color: primaryColor, fontWeight: FontWeight.bold),
),
centerTitle: true,
),
body: Stack(
children: <Widget>[
Center(
child: FlatButton(
onPressed: handleSignIn,
child: Text(
'SIGN IN WITH GOOGLE',
style: TextStyle(fontSize: 16.0),
),
color: Color(0xffdd4b39),
highlightColor: Color(0xffff7f7f),
splashColor: Colors.transparent,
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(30.0, 15.0, 30.0, 15.0)),
),
// Loading
Positioned(
child: isLoading
? Container(
child: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(themeColor),
),
),
color: Colors.white.withOpacity(0.8),
)
: Container(),
),
],
));
}
}
Ответ 7
Вместо этого вы можете использовать виджет FutureBuilder. Это принимает аргумент, который должен быть будущим. Затем вы можете использовать моментальный снимок, который является состоянием в момент асинхронного вызова при входе в систему, как только он завершится, состояние асинхронной функции будет возвращено, и будущий сборщик восстановит себя, чтобы вы могли запросить новый государство.
FutureBuilder(
future: myFutureFunction(),
builder: (context, AsyncSnapshot<List<item>> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
//Send the user to the next page.
},
);
Здесь у вас есть пример того, как построить будущее
Future<void> myFutureFunction() async{
await callToApi();}
Ответ 8
class Loader extends StatefulWidget {
@override
State createState() => LoaderState();
}
class LoaderState extends State<Loader> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<double> animation;
@override
void initState() {
super.initState();
controller = AnimationController(
duration: Duration(milliseconds: 1200), vsync: this);
animation = CurvedAnimation(parent: controller, curve: Curves.elasticOut);
animation.addListener(() {
this.setState(() {});
});
animation.addStatusListener((AnimationStatus status) {});
controller.repeat();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
color: Colors.blue,
height: 3.0,
width: animation.value * 100.0,
),
Padding(
padding: EdgeInsets.only(bottom: 5.0),
),
Container(
color: Colors.blue[300],
height: 3.0,
width: animation.value * 75.0,
),
Padding(
padding: EdgeInsets.only(bottom: 5.0),
),
Container(
color: Colors.blue,
height: 3.0,
width: animation.value * 50.0,
)
],
);
}
}
Expanded(
child: Padding(
padding:
EdgeInsets.only(left: 20.0, right: 5.0, top:20.0),
child: GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FirstScreen()));
},
child: Container(
alignment: Alignment.center,
height: 45.0,
decoration: BoxDecoration(
color: Color(0xFF1976D2),
borderRadius: BorderRadius.circular(9.0)),
child: Text('Login',
style: TextStyle(
fontSize: 20.0, color: Colors.white))),
),
),
),
Ответ 9
Проверьте эту библиотеку progess_dialog
Ответ 10
Вы можете сделать это для центрального прозрачного индикатора прогресса
Future<Null> _submitDialog(BuildContext context) async {
return await showDialog<Null>(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return SimpleDialog(
elevation: 0.0,
backgroundColor: Colors.transparent,
children: <Widget>[
Center(
child: CircularProgressIndicator(),
)
],
);
});
}