#flutter #dart #setstate
Вопрос:
У меня есть домашняя страница с отслеживанием состояния, на которой есть список дочерних виджетов с отслеживанием состояния. Когда я нажимаю на ребенка, я собираюсь вызвать его setState()
, чтобы добавить к нему циркуляр-индикатор. Все это прекрасно и прекрасно; нажатие на ребенка только восстанавливает этого ребенка.
Тем не менее, у меня также есть домашняя страница , завернутая в an AbsorbPointer
, и я хочу установить absorbing = true
, когда я нажимаю на дочерний виджет. Цель состоит в том, чтобы запретить пользователю щелкать по экрану, пока приложение выполняет некоторую асинхронную работу в фоновом режиме. Проблема сейчас в том, что если я вызову setState()
домашнюю страницу, чтобы установить значение «поглощение» в значение true, она перестроит все дочерние виджеты.
Я мог бы передать некоторые параметры в дочерние виджеты, чтобы только тот, на который я нажал, имел циклический указатель, но даже тогда все остальные дочерние виджеты все равно будут перестроены.
Я предполагаю, что это сводится к тому факту, что я не могу вызвать setState() в родительском виджете без перестройки всех дочерних элементов, даже если параметр, который я передаю этому setState () ( absorbing
), не имеет ничего общего с этими дочерними элементами.
Есть ли обходной путь для этого?
Спасибо!
// home_screen.dart
class HomeScreen extends StatefulWidget {
static const String routeName = "homeScreen";
final MyUser? user;
const HomeScreen({Key? key, required this.user}) : super(key: key);
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
final MyDatabase _db = MyDatabase();
MyUser? _me;
int _currentPage = -1;
bool _isLoading = false;
...
@override
Widget build(BuildContext context) {
return AbsorbPointer(
absorbing: _isLoading,
child: Container(
decoration: BoxDecoration(
// color: Color(0xFF0d0717),
image: DecorationImage(
image: Image.asset(
'assets/background.png',
).image,
fit: BoxFit.cover,
),
),
child: Scaffold(
backgroundColor: Colors.transparent,
appBar: ...,
bottomNavigationBar: ...,
body: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 12.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
StreamBuilder<QuerySnapshot>(
stream: _db.getLiveChannels(),
builder: (_, snapshot) {
if (!snapshot.hasData) {
// print("Has no data");
return Center(
child: CircularProgressIndicator(),
);
}
_channels.addAll(List.generate(
snapshot.data!.docs.length,
(index) => Channel.fromSnapshot(
snapshot.data!.docs[index])));
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
'Now Playing',
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.w700,
),
),
SizedBox(width: 8.0),
LiveIndicator(),
],
),
SizedBox(height: 8.0),
Container(
height: 250,
child: PageView.builder(
physics: PageScrollPhysics(),
scrollDirection: Axis.horizontal,
controller: PageController(
viewportFraction: .9,
),
itemCount: _channels.length,
itemBuilder: (context, index) {
Channel channel =
_channels[_channels.length - 1 - index];
return ChildWidget(
callback: _callback;
loading: (_isLoading amp;amp; _currentPage == index),
key: UniqueKey(),
);
},
),
),
...,
],
);
},
),
...,
],
),
),
),
),
),
);
}
Future<void> _callback(params) async {
if (_isLoading == false) {
setState(() {
_isLoading = true;
_currentPage = index;
});
}
someAsyncMethod().then((_) => setState(() {
_isLoading = false;
_currentPage = -1;
}));
}
}
// child_widget.dart
class ChildWidget extends StatefulWidget {
final Future<void> Function(params) callback;
final bool loading;
const ChildWidget({
Key? key,
required this.callback,
required this.loading,
}) : super(key: key);
@override
_ChildWidgetState createState() => _ChildWidgetState();
}
class _ChildWidgetState extends State<ChildWidget> {
late Future<void> Function(params) callback;
late bool loading;
@override
void initState() {
super.initState();
callback = widget.callback;
loading = widget.loading;
}
@override
Widget build(BuildContext context) {
return Padding(
child: Column(
children: [
Expanded(
child: CustomClickableWidget(
onPressed: callback,
child: Expanded(
child: Container(
child: Stack(
children: [
...,
if (loading) ...[
Container(
alignment: Alignment.center,
child: CircularProgressIndicator(),
),
],
],
),
),
),
),
),
...,
],
),
);
}
}
Ответ №1:
Функция setState запускает функцию Build (), поэтому весь код, присутствующий в функции Build (), будет выполнен снова. Я не совсем понимаю, почему это для вас проблема ?
С другой стороны, в вашем коде я вижу, что для вашего ребенка вы определили ключ: uniqueKey (). Когда функция сборки будет запущена после setState (), она создаст новый дочерний элемент без сохранения состояния предыдущего дочернего элемента. Вы не должны определять uniqueKey () в своей функции, а скорее как переменную экземпляра вашего состояния
ChildWidget(callback: _callback;
loading: (_isLoading amp;amp; _currentPage == index),
key: UniqueKey(),
)
Вы должны определить свой ключ здесь
class _ChildWidgetState extends State<ChildWidget> {
UniqueKey myKey = UniqueKey();
и вы функционируете
ChildWidget(callback: _callback;
loading: (_isLoading amp;amp; _currentPage == index),
key: myKey,
)
Комментарии:
1. Спасибо за комментарий! Дело в том, что я хочу каким-то образом перестроить только дочерний элемент, на который я нажимаю, чтобы он показывал индикатор загрузки (как на скриншоте), но также изменить
absorbing
параметр указателя загрузки в родительском виджете. Не похоже, что есть способ, так что я думаю, что я ищу обходной путь. Моя цель состоит в том, чтобы 1) указать, что я нажал на конкретного ребенка, и 2) запретить пользователю нажимать/прокручивать приложение во время выполнения фоновой задачи.2. Удалите создание uniqueKey в функции Build (). а также я вижу, как вы инициализируете переменную «Загрузка» в своем исходном состоянии childwidget. поэтому эта переменная определяется только один раз и не меняется во время перестройки вашего ребенка, проблема может возникнуть и оттуда
3. Теперь это сработало! Я не знаю, потому ли это, что я удалил уникальный ключ, как вы сказали, или потому, что раньше были некоторые проблемы с моим ожиданием/асинхронностью. Но после исправления этого я теперь мог вызвать setState() в дочернем устройстве, чтобы переключить его индикатор загрузки, и вызвать setState() в родительском, чтобы переключить его переменную _isLoading.