Я новичок во флаттере и хотел знать, что лучше добавить CircularProgressIndicator
в мой макет. Например, мое представление входа в систему. Это представление имеет имя пользователя, пароль и кнопку входа в систему. Я действительно хотел создать макет наложения (с Opacity
), который при загрузке показывал индикатор прогресса, как я использую в NativeScript, но я немного запутался в том, как это сделать, и тоже, если это лучший способ. В NativeScript, например, я добавляю IndicatorActivity в основной макет и устанавливаю значение busy в true или false, чтобы он перекрывал все компоненты представления при загрузке.
Редактировать:
Мне удалось добиться такого результата:
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),
),
);
}
}
Я все еще приспосабливаюсь к идее состояний. Этот код находится в пределах ожидаемого при работе с флаттером?
Благодарность!
Ответы:
Во флаттере есть несколько способов справиться с асинхронными действиями.
Ленивый способ сделать это - использовать модальное окно. Что заблокирует ввод пользователя, тем самым предотвращая любые нежелательные действия. Это потребует очень небольших изменений в вашем коде. Просто измените ваш
_onLoading
на что-то вроде этого: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(); }); }
Самый идеальный способ сделать это - использовать
FutureBuilder
виджет с отслеживанием состояния. Что вы начали. Хитрость в том, что вместо того, чтобы иметьboolean loading = false
в своем состоянии, вы можете напрямую использоватьFuture<MyUser> user
А затем передайте его в качестве аргумента
FutureBuilder
, который предоставит вам некоторую информацию, такую как «hasData» или экземпляр поMyUser
завершении.Это привело бы к примерно следующему:
@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), ); } }, ); } }
источник
Navigator.pushNamed("/home")
.Для меня один из лучших способов сделать это - показать
SnackBar
внизу, когда происходит процесс входа в систему, это пример того, что я имею в виду:Вот как настроить
SnackBar
.Определите глобальный ключ для вашего
Scaffold
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Добавьте это в свой
Scaffold
key
атрибутreturn new Scaffold( key: _scaffoldKey, .......
Обратный
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") ); }
Это действительно зависит от того, как вы хотите создать свой макет, и я не уверен, что вы имеете в виду.
редактировать
Вы, вероятно, хотите, чтобы это было так, я использовал стек для достижения этого результата и просто отображал или скрывал свой индикатор на основе
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,), ],)); } }
источник
_loading
изменении все представления перестраиваются. Это так?Создать bool
isLoading
и установите для него значениеfalse
. С помощью тернарного оператора, когда пользователь нажимает кнопку входа в систему, устанавливает состояниеisLoading
наtrue
. Вместо кнопки входа вы получите круговой индикатор загрузки.isLoading ? new PrimaryButton( key: new Key('login'), text: 'Login', height: 44.0, onPressed: setState((){isLoading = true;})) : Center( child: CircularProgressIndicator(), ),
Вы можете увидеть скриншоты, как это выглядит, пока не щелкнули логин
После нажатия на логин
Тем временем вы можете запустить процесс входа в систему и войти в систему. Если учетные данные неверны , то вы снова будете
setState
в ,isLoading
чтобыfalse
, таким образом, чтобы индикатор загрузки станет невидимым и Войти кнопку видимой для пользователя. Кстати, primaryButton, используемый в коде, - это моя настраиваемая кнопка. Вы можете сделать то же самое сOnPressed
inbutton
.источник
1. Без плагина
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. С плагином
проверьте этот плагин progress_hud
добавить зависимость в файл pubspec.yaml
импортировать пакет
import 'package:progress_hud/progress_hud.dart';
Ниже приведен пример кода для отображения и скрытия индикатора.
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; } }
источник
Шаг 1. Создайте диалог
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; }, ); }
Шаг 2: Назовите это
showAlertDialog(context); await firebaseAuth.signInWithEmailAndPassword(email: email, password: password); Navigator.pop(context);
Пример с диалогом и формой входа
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), ],) ,),) ), ); } }
источник
Я использовал следующий подход, в котором используется простой виджет модального индикатора выполнения, который обертывает все, что вы хотите сделать модальным во время асинхронного вызова.
В примере в пакете также рассматривается, как обрабатывать проверку формы при выполнении асинхронных вызовов для проверки формы ( подробности этой проблемы см. В разделе flutter / issues / 9688 ). Например, не выходя из формы, этот метод проверки асинхронной формы можно использовать для проверки нового имени пользователя на соответствие существующим именам в базе данных при регистрации.
https://pub.dartlang.org/packages/modal_progress_hud
Вот демонстрация примера, поставляемого с пакетом (с исходным кодом):
Пример можно адаптировать к другому модальному поведению индикатора прогресса (например, к другой анимации, дополнительному тексту в модальном окне и т. Д.).
источник
Это мое решение со стеком
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(), ), ], )); } }
источник
Предлагаю использовать этот плагин flutter_easyloading
flutter_easyloading - чистый и легкий виджет загрузки для приложения Flutter, простой в использовании без контекста, поддерживает iOS и Android
Добавьте это в
pubspec.yaml
файл вашего пакета :dependencies: flutter_easyloading: ^2.0.0
Теперь в вашем коде Dart вы можете использовать:
import 'package:flutter_easyloading/flutter_easyloading.dart';
Чтобы использовать First, инициализируйте
FlutterEasyLoading
вMaterialApp
/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(); }
Затем используйте в соответствии с вашим требованием
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(); }, ), ), ); } }
источник
Вместо этого вы можете использовать виджет FutureBuilder. Это требует аргумента, который должен быть Будущим. Затем вы можете использовать моментальный снимок, который является состоянием во время асинхронного вызова при входе в систему, после его завершения состояние возврата асинхронной функции будет обновлено, и будущий конструктор перестроит себя, чтобы затем вы могли запросить новый штат.
FutureBuilder( future: myFutureFunction(), builder: (context, AsyncSnapshot<List<item>> snapshot) { if (!snapshot.hasData) { return Center( child: CircularProgressIndicator(), ); } else { //Send the user to the next page. }, );
Вот вам пример того, как построить будущее
Future<void> myFutureFunction() async{ await callToApi();}
источник
Вы можете сделать это для центрального прозрачного индикатора прогресса
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(), ) ], ); }); }
источник
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))), ), ), ),
источник
{ 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())); } }
источник