Sou novato em flutter e queria saber qual a melhor forma de adicionar CircularProgressIndicator
no meu layout. Por exemplo, minha visualização de login. Esta visão tem nome de usuário, senha e botão de login. Eu queria criar um layout de sobreposição (com Opacity
) que, ao carregar, mostrasse o indicador de progresso como eu uso no NativeScript, mas estou um pouco confuso sobre como fazer e também se é a melhor maneira. No NativeScript, por exemplo, adiciono IndicatorActivity no layout principal e defini ocupado como verdadeiro ou falso, de forma que ele sobreponha todos os componentes de exibição ao carregar.
Editar:
Consegui chegar a este resultado:
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),
),
);
}
}
Ainda estou me adaptando à ideia de estados. Esse código está dentro do esperado ao trabalhar com flutter?
Obrigado!
Respostas:
Na vibração, existem algumas maneiras de lidar com ações assíncronas.
Uma maneira preguiçosa de fazer isso pode ser usando um modal. O que irá bloquear a entrada do usuário, evitando ações indesejadas. Isso exigiria muito poucas alterações em seu código. Apenas modificando seu
_onLoading
para algo assim: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(); }); }
A maneira mais ideal de fazer isso é usando
FutureBuilder
um widget com estado. Que é o que você começou. O truque é que, em vez de ter umboolean loading = false
em seu estado, você pode usar diretamente umFuture<MyUser> user
Em seguida, passe-o como argumento para
FutureBuilder
, que fornecerá algumas informações como "hasData" ou a instância deMyUser
quando concluído.Isso levaria a algo assim:
@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), ); } }, ); } }
fonte
Navigator.pushNamed("/home")
.Para mim, uma maneira legal de fazer isso é mostrar um
SnackBar
na parte inferior enquanto o processo de login é realizado. Este é um exemplo do que quero dizer:Aqui está como configurar o
SnackBar
.Defina uma chave global para o seu
Scaffold
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Adicione ao seu
Scaffold
key
atributoreturn new Scaffold( key: _scaffoldKey, .......
Retorno do botão de login
onPressed
: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") ); }
Realmente depende de como você deseja construir seu layout, e não tenho certeza do que você tem em mente.
Editar
Você provavelmente quer assim, eu usei um Stack para alcançar este resultado e apenas mostrar ou ocultar meu indicador com base
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,), ],)); } }
fonte
_loading
muda, todas as visualizações são reconstruídas. É assim mesmo?Crie um bool
isLoading
e defina-o comofalse
. Com a ajuda do operador ternário, quando o usuário clica no botão de login, defina o estado deisLoading
paratrue
. Você obterá um indicador de carregamento circular no lugar do botão de loginisLoading ? new PrimaryButton( key: new Key('login'), text: 'Login', height: 44.0, onPressed: setState((){isLoading = true;})) : Center( child: CircularProgressIndicator(), ),
Você pode ver as capturas de tela como fica antes de clicar no login
Depois que o login é clicado
Nesse meio tempo, você pode executar o processo de login e login do usuário. Se as credenciais do usuário estão errados em seguida, novamente você vai
setState
deisLoading
afalse
, tal que o indicador de carga se tornará botão invisível e login visível para o usuário. A propósito, primaryButton usado no código é meu botão personalizado. Você pode fazer o mesmo comOnPressed
embutton
.fonte
1. Sem plugin
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. Com plugin
verifique este plugin progress_hud
adicione a dependência no arquivo pubspec.yaml
importar o pacote
import 'package:progress_hud/progress_hud.dart';
O código de amostra é fornecido abaixo para mostrar e ocultar o indicador
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; } }
fonte
Etapa 1: Criar diálogo
showAlertDialog(BuildContext context){ AlertDialog alert=AlertDialog( content: new Row( children: [ CircularProgressIndicator(), Container(margin: EdgeInsets.only(left: 5),child:Text("Loading" )), ],), ); showDialog(barrierDismissible: false, context:context, builder:(BuildContext context){ return alert; }, ); }
Etapa 2: Ligue para ele
showAlertDialog(context); await firebaseAuth.signInWithEmailAndPassword(email: email, password: password); Navigator.pop(context);
Exemplo com diálogo e formulário de login
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; class DynamicLayout extends StatefulWidget{ @override State<StatefulWidget> createState() { // TODO: implement createState return new MyWidget(); } } showAlertDialog(BuildContext context){ AlertDialog alert=AlertDialog( content: new Row( children: [ CircularProgressIndicator(), Container(margin: EdgeInsets.only(left: 5),child:Text("Loading" )), ],), ); showDialog(barrierDismissible: false, context:context, builder:(BuildContext context){ return alert; }, ); } class MyWidget extends State<DynamicLayout>{ Color color = Colors.indigoAccent; String title='app'; GlobalKey<FormState> globalKey=GlobalKey<FormState>(); String email,password; login() async{ var currentState= globalKey.currentState; if(currentState.validate()){ currentState.save(); FirebaseAuth firebaseAuth=FirebaseAuth.instance; try { showAlertDialog(context); AuthResult authResult=await firebaseAuth.signInWithEmailAndPassword( email: email, password: password); FirebaseUser user=authResult.user; Navigator.pop(context); }catch(e){ print(e); } }else{ } } @override Widget build(BuildContext context) { return new Scaffold( appBar:AppBar( title: Text("$title"), ) , body: Container(child: Form( key: globalKey, child: Container( padding: EdgeInsets.all(10), child: Column(children: <Widget>[ TextFormField(decoration: InputDecoration(icon: Icon(Icons.email),labelText: 'Email'), // ignore: missing_return validator:(val){ if(val.isEmpty) return 'Please Enter Your Email'; }, onSaved:(val){ email=val; }, ), TextFormField(decoration: InputDecoration(icon: Icon(Icons.lock),labelText: 'Password'), obscureText: true, // ignore: missing_return validator:(val){ if(val.isEmpty) return 'Please Enter Your Password'; }, onSaved:(val){ password=val; }, ), RaisedButton(color: Colors.lightBlue,textColor: Colors.white,child: Text('Login'), onPressed:login), ],) ,),) ), ); } }
fonte
Adotei a abordagem a seguir, que usa um widget indicador de progresso modal simples que envolve tudo o que você deseja tornar modal durante uma chamada assíncrona.
O exemplo no pacote também aborda como lidar com a validação de formulário ao fazer chamadas assíncronas para validar o formulário (consulte flutter / issues / 9688 para obter detalhes sobre esse problema). Por exemplo, sem sair do formulário, este método de validação de formulário assíncrono pode ser usado para validar um novo nome de usuário em relação aos nomes existentes em um banco de dados durante a inscrição.
https://pub.dartlang.org/packages/modal_progress_hud
Aqui está a demonstração do exemplo fornecido com o pacote (com o código-fonte):
O exemplo pode ser adaptado a outro comportamento do indicador de progresso modal (como diferentes animações, texto adicional no modal, etc.).
fonte
Esta é a minha solução com pilha
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(), ), ], )); } }
fonte
Eu sugiro usar este plugin flutter_easyloading
flutter_easyloading é um widget de carregamento simples e leve para o aplicativo Flutter, fácil de usar sem contexto, compatível com iOS e Android
Adicione isto ao
pubspec.yaml
arquivo do seu pacote :dependencies: flutter_easyloading: ^2.0.0
Agora, em seu código Dart, você pode usar:
import 'package:flutter_easyloading/flutter_easyloading.dart';
Para usar primeiro, inicialize
FlutterEasyLoading
emMaterialApp
/CupertinoApp
import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import './custom_animation.dart'; import './test.dart'; void main() { runApp(MyApp()); configLoading(); } void configLoading() { EasyLoading.instance ..displayDuration = const Duration(milliseconds: 2000) ..indicatorType = EasyLoadingIndicatorType.fadingCircle ..loadingStyle = EasyLoadingStyle.dark ..indicatorSize = 45.0 ..radius = 10.0 ..progressColor = Colors.yellow ..backgroundColor = Colors.green ..indicatorColor = Colors.yellow ..textColor = Colors.yellow ..maskColor = Colors.blue.withOpacity(0.5) ..userInteractions = true ..customAnimation = CustomAnimation(); }
Em seguida, use de acordo com sua necessidade
import 'package:flutter/material.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:dio/dio.dart'; class TestPage extends StatefulWidget { @override _TestPageState createState() => _TestPageState(); } class _TestPageState extends State<TestPage> { @override void initState() { super.initState(); // EasyLoading.show(); } @override void deactivate() { EasyLoading.dismiss(); super.deactivate(); } void loadData() async { try { EasyLoading.show(); Response response = await Dio().get('https://github.com'); print(response); EasyLoading.dismiss(); } catch (e) { EasyLoading.showError(e.toString()); print(e); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('Flutter EasyLoading'), ), body: Center( child: FlatButton( textColor: Colors.blue, child: Text('loadData'), onPressed: () { loadData(); // await Future.delayed(Duration(seconds: 2)); // EasyLoading.show(status: 'loading...'); // await Future.delayed(Duration(seconds: 5)); // EasyLoading.dismiss(); }, ), ), ); } }
fonte
Você pode usar o widget FutureBuilder. Isso requer um argumento que deve ser um futuro. Em seguida, você pode usar um instantâneo que é o estado no momento da chamada assíncrona ao fazer login, uma vez que termina o estado de retorno da função assíncrona será atualizado e o futuro construtor se reconstruirá para que você possa solicitar o novo Estado.
FutureBuilder( future: myFutureFunction(), builder: (context, AsyncSnapshot<List<item>> snapshot) { if (!snapshot.hasData) { return Center( child: CircularProgressIndicator(), ); } else { //Send the user to the next page. }, );
Aqui você tem um exemplo de como construir um futuro
Future<void> myFutureFunction() async{ await callToApi();}
fonte
Você pode fazer isso para o indicador de progresso transparente central
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(), ) ], ); }); }
fonte
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))), ), ), ),
fonte
{ isloading? progressIos:Container() progressIos(int i) { return Container( color: i == 1 ? AppColors.liteBlack : i == 2 ? AppColors.darkBlack : i == 3 ? AppColors.pinkBtn : '', child: Center(child: CupertinoActivityIndicator())); } }
fonte