Je suis novice en flutter et je voulais savoir quelle est la meilleure façon d'ajouter CircularProgressIndicator
dans ma mise en page. Par exemple, ma vue de connexion. Cette vue a un nom d'utilisateur, un mot de passe et un bouton de connexion. Je voulais créer une mise en page de superposition (avec Opacity
) qui, lors du chargement, affiche un indicateur de progression comme j'utilise dans NativeScript, mais je suis peu confus avec comment faire et aussi si c'est la meilleure façon. Sur NativeScript, par exemple, j'ajoute IndicatorActivity dans la mise en page principale et définit busy sur true ou false, de sorte qu'il superpose tous les composants de la vue lors du chargement.
Éditer:
J'ai pu atteindre ce résultat:
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),
),
);
}
}
Je m'adapte toujours à l'idée des États. Ce code est conforme aux attentes lorsque vous travaillez avec le flutter?
Merci!
Réponses:
Dans le flutter, il existe plusieurs façons de gérer les actions asynchrones.
Une façon paresseuse de le faire peut être d'utiliser un modal. Ce qui bloquera l'entrée de l'utilisateur, empêchant ainsi toute action indésirable. Cela nécessiterait très peu de modifications de votre code. Modifiez simplement votre
_onLoading
pour quelque chose comme ceci: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(); }); }
La manière la plus idéale de le faire est d'utiliser
FutureBuilder
un widget avec état. C'est ce que vous avez commencé. L'astuce est qu'au lieu d'avoir unboolean loading = false
dans votre état, vous pouvez directement utiliser unFuture<MyUser> user
Et puis passez-le comme argument à
FutureBuilder
, ce qui vous donnera des informations telles que "hasData" ou l'instance de uneMyUser
fois terminé.Cela conduirait à quelque chose comme ceci:
@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), ); } }, ); } }
la source
Navigator.pushNamed("/home")
.Pour moi, une façon intéressante de le faire est d'afficher un
SnackBar
en bas pendant le processus de connexion, ceci est un exemple de ce que je veux dire:Voici comment configurer le
SnackBar
.Définissez une clé globale pour votre
Scaffold
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Ajoutez-le à votre
Scaffold
key
attributreturn new Scaffold( key: _scaffoldKey, .......
Rappel de mon bouton de connexion
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") ); }
Cela dépend vraiment de la façon dont vous souhaitez créer votre mise en page et je ne suis pas sûr de ce que vous avez en tête.
Éditer
Vous le souhaitez probablement de cette façon, j'ai utilisé une pile pour obtenir ce résultat et simplement afficher ou masquer mon indicateur en fonction de
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,), ],)); } }
la source
_loading
changement, toutes les vues sont reconstruites. Est-ce vrai?Créez un booléen
isLoading
et définissez-le surfalse
. Avec l'aide de l'opérateur ternaire, lorsque l'utilisateur clique sur le bouton de connexion, définissez l'étatisLoading
surtrue
. Vous obtiendrez un indicateur de chargement circulaire à la place du bouton de connexionisLoading ? new PrimaryButton( key: new Key('login'), text: 'Login', height: 44.0, onPressed: setState((){isLoading = true;})) : Center( child: CircularProgressIndicator(), ),
Vous pouvez voir les captures d'écran à quoi il ressemble avant de cliquer sur la connexion
Après avoir cliqué sur la connexion
En attendant, vous pouvez exécuter le processus de connexion et vous connecter à l'utilisateur. Si les informations d' identification de l' utilisateur sont erronés puis de nouveau vous
setState
deisLoading
lafalse
sorte que l' indicateur de chargement deviendras bouton invisible et visible connexion à l' utilisateur. À propos, primaryButton utilisé dans le code est mon bouton personnalisé. Vous pouvez faire de même avecOnPressed
inbutton
.la source
1. Sans 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. Avec plugin
vérifier ce plugin progress_hud
ajouter la dépendance dans le fichier pubspec.yaml
importer le package
import 'package:progress_hud/progress_hud.dart';
Un exemple de code est donné ci-dessous pour afficher et masquer l'indicateur
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; } }
la source
Étape 1: Créer une boîte de dialogue
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; }, ); }
Étape 2: Appelez-le
showAlertDialog(context); await firebaseAuth.signInWithEmailAndPassword(email: email, password: password); Navigator.pop(context);
Exemple avec boîte de dialogue et formulaire de connexion
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), ],) ,),) ), ); } }
la source
J'ai adopté l'approche suivante, qui utilise un simple widget indicateur de progression modale qui encapsule tout ce que vous voulez rendre modal lors d'un appel asynchrone.
L'exemple du package explique également comment gérer la validation de formulaire tout en effectuant des appels asynchrones pour valider le formulaire (voir flutter / issues / 9688 pour plus de détails sur ce problème). Par exemple, sans quitter le formulaire, cette méthode de validation de formulaire asynchrone peut être utilisée pour valider un nouveau nom d'utilisateur par rapport aux noms existants dans une base de données lors de l'inscription.
https://pub.dartlang.org/packages/modal_progress_hud
Voici la démo de l'exemple fourni avec le package (avec le code source):
L'exemple pourrait être adapté à d'autres comportements d'indicateur de progression modale (comme différentes animations, texte supplémentaire en modal, etc.).
la source
C'est ma solution avec stack
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(), ), ], )); } }
la source
Je suggère d'utiliser ce plugin flutter_easyloading
flutter_easyloading est un widget de chargement propre et léger pour l'application Flutter, facile à utiliser sans contexte, prend en charge iOS et Android
Ajoutez ceci au
pubspec.yaml
fichier de votre package :dependencies: flutter_easyloading: ^2.0.0
Maintenant, dans votre code Dart, vous pouvez utiliser:
import 'package:flutter_easyloading/flutter_easyloading.dart';
Pour utiliser First, initialisez
FlutterEasyLoading
dansMaterialApp
/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(); }
Ensuite, utilisez selon vos besoins
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(); }, ), ), ); } }
la source
Vous pouvez utiliser le widget FutureBuilder à la place. Cela prend un argument qui doit être un avenir. Ensuite, vous pouvez utiliser un instantané qui est l'état au moment de l'appel asynchrone lors de la connexion, une fois qu'il se termine, l'état du retour de la fonction async sera mis à jour et le futur constructeur se reconstruira afin que vous puissiez alors demander le nouveau Etat.
FutureBuilder( future: myFutureFunction(), builder: (context, AsyncSnapshot<List<item>> snapshot) { if (!snapshot.hasData) { return Center( child: CircularProgressIndicator(), ); } else { //Send the user to the next page. }, );
Ici, vous avez un exemple sur la façon de construire un avenir
Future<void> myFutureFunction() async{ await callToApi();}
la source
Vous pouvez le faire pour l'indicateur de progression transparent du centre
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(), ) ], ); }); }
la source
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))), ), ), ),
la source
{ 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())); } }
la source