Jestem nowicjuszem w trzepotaniu i chciałem wiedzieć, jaki jest lepszy sposób na dodanie CircularProgressIndicator
do mojego układu. Na przykład mój widok logowania. Ten widok ma nazwę użytkownika, hasło i przycisk logowania. Chciałem stworzyć układ nakładki (z Opacity
), który podczas ładowania wyświetla wskaźnik postępu, tak jak ja używam w NativeScript, ale nie jestem zdezorientowany, jak to zrobić, a także, czy jest to lepszy sposób. Na przykład w języku NativeScript dodaję IndicatorActivity w głównym układzie i ustawiam zajęty na true lub false, aby podczas ładowania nakładał się na wszystkie składniki widoku.
Edytować:
Udało mi się osiągnąć taki wynik:
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),
),
);
}
}
Nadal dostosowuję się do idei stanów. Ten kod jest zgodny z oczekiwaniami podczas pracy z flutter?
Dzięki!
Odpowiedzi:
W przypadku flutter istnieje kilka sposobów radzenia sobie z akcjami asynchronicznymi.
Leniwy sposób na zrobienie tego może polegać na użyciu modalu. Który blokuje wprowadzanie danych przez użytkownika, zapobiegając w ten sposób niepożądanym działaniom. Wymagałoby to bardzo niewielkich zmian w kodzie. Po prostu zmodyfikuj swój
_onLoading
na coś takiego: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(); }); }
Najlepszym sposobem na to jest użycie
FutureBuilder
i widgetu stanowego. Od czego zacząłeś. Sztuczka polega na tym, że zamiast miećboolean loading = false
w swoim stanie, możesz bezpośrednio użyćFuture<MyUser> user
A następnie przekaż go jako argument do
FutureBuilder
, co da ci pewne informacje, takie jak „hasData” lub wystąpienieMyUser
po zakończeniu.Prowadziłoby to do czegoś takiego:
@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), ); } }, ); } }
źródło
Navigator.pushNamed("/home")
.Dla mnie jednym fajnym sposobem na zrobienie tego jest pokazanie
SnackBar
u dołu podczas procesu logowania, oto przykład tego, co mam na myśli:Oto jak skonfigurować
SnackBar
.Zdefiniuj globalny klucz dla swojego
Scaffold
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Dodaj to do swojego
Scaffold
key
atrybutureturn new Scaffold( key: _scaffoldKey, .......
Oddzwanianie do mojego przycisku
onPressed
logowania: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") ); }
To naprawdę zależy od tego, jak chcesz zbudować swój układ i nie jestem pewien, co masz na myśli.
Edytować
Prawdopodobnie chcesz tego w ten sposób, użyłem stosu, aby osiągnąć ten wynik i po prostu pokaż lub ukryj mój wskaźnik na podstawie
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,), ],)); } }
źródło
_loading
zmianie wszystkie widoki są odbudowywane. Czy tak jest?Utwórz bool
isLoading
i ustaw go nafalse
. Za pomocą operatora trójargumentowego, gdy użytkownik kliknie przycisk logowania, ustawia stanisLoading
natrue
. Zamiast przycisku logowania otrzymasz okrągły wskaźnik ładowaniaisLoading ? new PrimaryButton( key: new Key('login'), text: 'Login', height: 44.0, onPressed: setState((){isLoading = true;})) : Center( child: CircularProgressIndicator(), ),
Możesz zobaczyć zrzuty ekranu, jak to wygląda, zanim klikniesz przed zalogowaniem
Po kliknięciu logowania
W międzyczasie możesz uruchomić proces logowania i zalogować użytkownika. Jeżeli poświadczenia użytkownika są błędne znowu będzie
setState
odisLoading
celufalse
, tak że wskaźnik ładowania staną się niewidoczne i logowanie przycisk widoczny dla użytkownika. Nawiasem mówiąc, primaryButton użyty w kodzie jest moim przyciskiem niestandardowym. Możesz zrobić to samo zOnPressed
inbutton
.źródło
1. Bez wtyczki
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. Z wtyczką
sprawdź tę wtyczkę progress_hud
dodaj zależność w pliku pubspec.yaml
zaimportuj pakiet
import 'package:progress_hud/progress_hud.dart';
Przykładowy kod jest podany poniżej, aby pokazać i ukryć wskaźnik
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; } }
źródło
Krok 1: Utwórz okno dialogowe
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; }, ); }
Krok 2: Zadzwoń
showAlertDialog(context); await firebaseAuth.signInWithEmailAndPassword(email: email, password: password); Navigator.pop(context);
Przykład z oknem i formularzem logowania
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), ],) ,),) ), ); } }
źródło
Przyjąłem następujące podejście, które używa prostego widżetu wskaźnika postępu modalnego, który zawija wszystko, co chcesz zrobić modalne podczas wywołania asynchronicznego.
Przykład w pakiecie dotyczy również sposobu obsługi walidacji formularza podczas wykonywania wywołań asynchronicznych w celu sprawdzenia poprawności formularza ( szczegóły tego problemu można znaleźć w flutter / Issues / 9688 ). Na przykład bez opuszczania formularza ta asynchroniczna metoda sprawdzania poprawności formularza może służyć do sprawdzania poprawności nowej nazwy użytkownika względem istniejących nazw w bazie danych podczas rejestracji.
https://pub.dartlang.org/packages/modal_progress_hud
Oto demo przykładu dostarczonego z pakietem (z kodem źródłowym):
Przykład można dostosować do innych zachowań wskaźnika postępu modalnego (takich jak różne animacje, dodatkowy tekst w trybie modalnym itp.).
źródło
To jest moje rozwiązanie ze stosem
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(), ), ], )); } }
źródło
Proponuję użyć tej wtyczki flutter_easyloading
flutter_easyloading jest przejrzystym i lekkim widgetem ładowania aplikacji Flutter, łatwym w użyciu bez kontekstu, obsługuje iOS i Android
Dodaj to do
pubspec.yaml
pliku pakietu :dependencies: flutter_easyloading: ^2.0.0
Teraz w swoim kodzie Dart możesz użyć:
import 'package:flutter_easyloading/flutter_easyloading.dart';
Aby użyć First, zainicjuj
FlutterEasyLoading
wMaterialApp
/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(); }
Następnie użyj zgodnie ze swoimi wymaganiami
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(); }, ), ), ); } }
źródło
Zamiast tego możesz użyć widgetu FutureBuilder. To wymaga argumentu, który musi być przyszłością. Następnie możesz użyć migawki, która jest aktualnym stanem wywołania asynchronicznego podczas logowania, po jego zakończeniu stan funkcji asynchronicznej zostanie zaktualizowany, a przyszły konstruktor odbuduje się, więc możesz poprosić o nowe stan.
FutureBuilder( future: myFutureFunction(), builder: (context, AsyncSnapshot<List<item>> snapshot) { if (!snapshot.hasData) { return Center( child: CircularProgressIndicator(), ); } else { //Send the user to the next page. }, );
Tutaj masz przykład, jak budować przyszłość
Future<void> myFutureFunction() async{ await callToApi();}
źródło
Możesz to zrobić dla środkowego przezroczystego wskaźnika postępu
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(), ) ], ); }); }
źródło
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))), ), ), ),
źródło
{ 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())); } }
źródło