Je suis débutant dans le flutter et je voulais savoir quel est le meilleur moyen d'ajouter CircularProgressIndicator
dans ma mise en page. Par exemple, ma vue de connexion. Cette vue possède un nom d'utilisateur, un mot de passe et un bouton de connexion. Je souhaitais créer une présentation superposée (avec Opacity
) qui, lors du chargement, affiche un indicateur de progression similaire à celui que j'utilise dans NativeScript, mais je ne suis pas très confus quant à la procédure à suivre et à la meilleure solution. Sur NativeScript, par exemple, j'ajoute IndicatorActivity dans la mise en page principale et j'affecte la valeur true ou false à Occupé. Ainsi, toutes les composantes de la vue sont superposées lors du chargement.
Modifier:
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 d'états. Ce code est-il conforme aux attentes lorsque vous travaillez avec Flutter?
Merci!
En mode flottant, il existe plusieurs moyens de gérer les actions asynchrones.
Une façon paresseuse de le faire peut être d’utiliser un modal. Ce qui bloquera la saisie de l'utilisateur, empêchant ainsi toute action indésirable . Cela nécessiterait très peu de modification de votre code. Modifiez simplement votre _onLoading
en quelque chose comme ceci:
void _onLoading() {
showDialog(
context: context,
barrierDismissible: false,
child: new 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();
});
}
Le moyen le plus idéal de le faire consiste à utiliser FutureBuilder
et un widget avec état. C’est ce que vous avez commencé à faire .. Le truc, c’est qu'au lieu d’avoir un boolean loading = false
dans votre état, vous pouvez utiliser directement un Future<MyUser> user
Puis transmettez-le en tant qu’argument à FutureBuilder
, ce qui vous donnera des informations telles que "hasData" ou l’instance de MyUser
à la fin.
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),
);
}
},
);
}
}
Pour moi, une bonne façon de faire est de montrer une SnackBar
en bas pendant le processus de connexion, il s'agit d'un exemple de ce que je veux dire:
Voici comment configurer la SnackBar
.
Définir une clé globale pour votre Scaffold
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Ajoutez-le à votre attribut Scaffold
key
return new Scaffold(
key: _scaffoldKey,
.......
Bouton My SignIn 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")
);
}
Cela dépend vraiment de la façon dont vous voulez construire votre mise en page, et je ne suis pas sûr de ce que vous avez en tête.
Modifier
Vous le voulez probablement ainsi, j'ai utilisé une pile pour obtenir ce résultat et il suffit d'afficher ou de 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,),
],));
}
}
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
ajoute la dépendance dans le fichier pubspec.yaml
dev_dependencies:
progress_hud:
importer le paquet
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;
}
}
Créez une variable isLoading
et définissez-la sur false
. Avec l'aide de l'opérateur ternaire, lorsque l'utilisateur clique sur le bouton de connexion, définissez l'état isLoading
sur true
. Vous obtiendrez un indicateur de chargement circulaire à la place du bouton de connexion
isLoading ? new PrimaryButton(
key: new Key('login'),
text: 'Login',
height: 44.0,
onPressed: SetState((){isLoading = ture;}))
: Center(
child: CircularProgressIndicator(),
),
Vous pouvez voir les captures d'écran à quoi il ressemble avant que la connexion ne soit cliquée
En attendant, vous pouvez exécuter le processus de connexion et connecter l'utilisateur. Si les informations d'identification de l'utilisateur sont incorrectes, vous devrez à nouveau setState
de isLoading
à false
, de sorte que l'indicateur de chargement devienne invisible et que le bouton de connexion soit visible pour l'utilisateur . Au fait, primaryButton utilisé dans le code est mon bouton personnalisé. Vous pouvez faire la même chose avec OnPressed
dans button
.
J'ai adopté l'approche suivante, qui utilise un simple indicateur d'indicateur de progression modal qui englobe tout ce que vous voulez rendre modal lors d'un appel asynchrone.
L'exemple du paquet explique également comment gérer la validation de formulaire lors d'appels asynchrones pour valider le formulaire (voir flutter/issues/9688 pour des 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 la signature.
https://pub.dartlang.org/packages/modal_progress_hud
Voici la démo de l'exemple fourni avec le paquet (avec le code source):
L'exemple pourrait être adapté à un autre comportement d'indicateur de progression modal (comme des animations différentes, du texte supplémentaire en mode modal, etc.).
Vous pouvez utiliser le widget FutureBuilder à la place. Cela prend un argument qui doit être un avenir. Vous pouvez ensuite utiliser un instantané qui correspond à l'état actuel de l'appel asynchrone lors de la connexion. Une fois l'opération terminée, l'état du retour de la fonction asynchrone sera mis à jour et le futur constructeur se reconstruira de manière à vous 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();}
Ceci est ma solution avec pile
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(),
),
],
));
}
}
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))),
),
),
),