Ich bin ein Neuling im Flattern und wollte wissen, wie ich CircularProgressIndicator
besser in mein Layout einfügen kann. Zum Beispiel meine Login-Ansicht. Diese Ansicht hat Benutzername, Passwort und Login-Button. Ich wollte ein Overlay-Layout (mit Opacity
) erstellen, das beim Laden den Fortschrittsindikator anzeigt, wie ich ihn in NativeScript verwende, aber ich bin wenig verwirrt mit der Vorgehensweise und auch, wenn dies der bessere Weg ist. Bei NativeScript füge ich beispielsweise IndicatorActivity im Hauptlayout hinzu und setze busy auf true oder false, damit alle Ansichtskomponenten beim Laden überlagert werden.
Bearbeiten:
Ich konnte dieses Ergebnis erreichen:
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),
),
);
}
}
Ich passe mich immer noch an die Idee von Staaten an. Dieser Code liegt im erwarteten Bereich, wenn mit Flattern gearbeitet wird?
Vielen Dank!
Beim Flattern gibt es einige Möglichkeiten, mit asynchronen Aktionen umzugehen.
Ein fauler Weg, dies zu tun, kann ein Modal sein. Dadurch werden die Benutzereingaben blockiert und somit unerwünschte Aktionen verhindert. Dies würde nur sehr wenig Änderungen am Code erfordern. Ändern Sie einfach Ihren _onLoading
in etwa wie folgt:
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();
});
}
Der beste Weg, dies zu tun, ist FutureBuilder
und ein stateful-Widget. Welches ist, was Sie angefangen haben .. Der Trick ist, dass Sie, anstatt einen boolean loading = false
in Ihrem Staat zu haben, einen Future<MyUser> user
direkt verwenden können.
Und dann übergeben Sie es als Argument an FutureBuilder
, das Ihnen Informationen wie "hasData" oder die Instanz von MyUser
liefert, wenn Sie fertig sind.
Dies würde zu so etwas führen:
@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),
);
}
},
);
}
}
Für mich ist es ein guter Weg, dies zu tun, wenn während des Anmeldeprozesses unten ein SnackBar
angezeigt wird. Dies ist ein Beispiel für das, was ich meine:
So richten Sie das SnackBar
ein.
Definieren Sie einen globalen Schlüssel für Ihre Scaffold
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Fügen Sie es Ihrem Scaffold
key
-Attribut hinzu
return new Scaffold(
key: _scaffoldKey,
.......
Mein SignIn-Button onPressed
-Rückruf:
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")
);
}
Es hängt wirklich davon ab, wie Sie Ihr Layout erstellen möchten, und ich bin mir nicht sicher, was Sie vorhaben.
Bearbeiten
Sie möchten es wahrscheinlich so, ich habe einen Stack verwendet, um dieses Ergebnis zu erzielen, und einfach meinen Indikator basierend auf onPressed
ein- oder ausblenden.
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. Ohne 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. Mit Plugin
überprüfe dieses Plugin progress_hud
fügen Sie die Abhängigkeit in der Datei pubspec.yaml hinzu
dev_dependencies:
progress_hud:
das Paket importieren
import 'package:progress_hud/progress_hud.Dart';
Im Folgenden finden Sie Beispielcode, um den Indikator ein- und auszublenden
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;
}
}
Erstellen Sie ein bool isLoading
und setzen Sie es auf false
. Wenn der Benutzer auf die Schaltfläche Anmelden klickt, setzen Sie den Status von isLoading
auf true
. Anstelle des Login-Buttons erhalten Sie eine Umlaufanzeige
isLoading ? new PrimaryButton(
key: new Key('login'),
text: 'Login',
height: 44.0,
onPressed: SetState((){isLoading = ture;}))
: Center(
child: CircularProgressIndicator(),
),
Sie können Screenshots sehen, wie es aussieht, während Sie auf das Login klicken
In der Zwischenzeit können Sie den Anmeldeprozess und den Anmeldebenutzer ausführen. Wenn die Anmeldeinformationen der Benutzer falsch sind, werden Sie wieder setState
von isLoading
in false
umbenannt, so dass der Ladeindikator unsichtbar wird und die Anmeldeschaltfläche für den Benutzer sichtbar ist . Im Code ist primaryButton übrigens meine benutzerdefinierte Schaltfläche. Sie können dasselbe mit OnPressed
in button
tun.
Ich habe den folgenden Ansatz gewählt, der ein einfaches modales Fortschrittsanzeiger-Widget verwendet, das alles umschließt, was Sie während eines asynchronen Aufrufs modal machen möchten.
In dem Beispiel im Paket wird auch beschrieben, wie die Formularüberprüfung bei asynchronen Aufrufen zur Überprüfung des Formulars ausgeführt wird (Einzelheiten hierzu finden Sie unter flutter/issues/9688 ). Ohne das Formular zu verlassen, kann diese async-Formularüberprüfungsmethode beispielsweise verwendet werden, um einen neuen Benutzernamen anhand der vorhandenen Namen in einer Datenbank zu überprüfen, während er sich anmeldet.
https://pub.dartlang.org/packages/modal_progress_hud
Hier ist die Demo des mit dem Paket gelieferten Beispiels (mit Quellcode):
Das Beispiel könnte an das Verhalten anderer modaler Statusanzeigen angepasst werden (z. B. verschiedene Animationen, zusätzlicher Text in modal usw.).
Sie können dies für die mittlere transparente Fortschrittsanzeige tun
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(),
)
],
);
});
}
Sie können stattdessen das FutureBuilder-Widget verwenden. Dies erfordert ein Argument, das eine Zukunft sein muss. Dann können Sie eine Momentaufnahme verwenden, die sich zum Zeitpunkt des asynchronen Aufrufs beim Anmelden befindet. Sobald der Status der async-Funktion beendet ist, wird return aktualisiert und der zukünftige Builder wird sich selbst neu aufbauen, sodass Sie nach dem neuen fragen können Zustand.
FutureBuilder(
future: myFutureFunction(),
builder: (context, AsyncSnapshot<List<item>> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
//Send the user to the next page.
},
);
Hier haben Sie ein Beispiel, wie Sie eine Zukunft aufbauen können
Future<void> myFutureFunction() async{
await callToApi();}
Dies ist meine Lösung mit 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(),
),
],
));
}
}
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))),
),
),
),