Flutter Draggable - как вернуть перетаскиваемое в исходное положение?
Обновления:
Смотрите последние обновления внизу.
Оригинал
У меня есть виджеты Flutter Draggable и DragTarget, которые работают, за исключением того, что после размещения DragTarget я попадаю в конкретную ситуацию, когда я хочу перестроить DragTarget и переместить Draggable обратно в исходное положение.
Когда я проверяю инспектор Flutter, он показывает мои виджеты там, где я их ожидал, но когда я вызываю своего провайдера для получения следующего набора информации, перетаскиваемые объекты остаются в исходном положении, а не возвращаются в исходное положение.
Как видно из последнего кадра изображения, три целевых кадра все еще содержат ссылку на перетаскиваемые виджеты, но я перестраиваю виджеты Draggable и DragTarget при каждом щелчке красной кнопки IconButton.
Наблюдения
- Перетаскиваемые объекты остаются в опущенном положении, даже если я оборачиваю плитки в родительском виджете с помощью Consumer.
- Потребитель передает измененные значения данных.
- DragTargets отображаются в моем дереве виджетов с помощью Flutter
Inspector. - Когда я дохожу до события, на котором у меня больше трех
перетаскиваемых элементов, перетаскиваемые объекты оказываются не на своем месте, в то время как плитки и DropTargets для других виджетов отображаются должным образом.
Ожидания:
Когда я сбрасываю свои потребительские данные, я хочу, чтобы плитки и цели были перерисованы в их исходных положениях с новыми данными.
DragTarget
@override
Widget build(BuildContext context) {
return DragTarget(
builder:
(context, List<String> candidateData, List<dynamic> rejectedData) {
return (isSuccessful) //&& !widget.isPuzzleComplete
? Tile(
title: widget.data,
)
: TargetPlaceholder(
widget: widget,
);
},
onWillAccept: (data) {
return widget.data == data;
},
onAccept: (data) {
setState(() {
Provider.of<GameController>(
context,
listen: false,
).checkPuzzleSolved();
//activate the animation, may not require setState
isSuccessful = !widget.isPuzzleComplete;
});
},
onLeave: (data) {
print('Draggable object left the target area containing data of $data');
},
);
}
}
Перетаскиваемый
@override
Widget build(BuildContext context) {
return Draggable(
data: widget.title,
// dragAnchor: DragAnchor.pointer,
child: isSuccessful ? Container() : TileContent(widget: widget),
childWhenDragging: Container(),
feedback: Opacity(
opacity: 0.7,
child: TileContent(
widget: widget,
),
),
onDragCompleted: () {
setState(() {
Transform(
transform: Matrix4.translation(_shake()),
child: TileContent(widget: widget));
});
},
onDragEnd: (details) {
setState(() {
isSuccessful = details.wasAccepted;
});
},
);
}
}
Родительский виджет
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
for (var item in targetPattern)
Consumer<GameController>(builder: (context, gc, _) {
return Target(
data: item,
isPuzzleComplete: gc.isPuzzleComplete,
);
})
],
),
SizedBox(
height: 36,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
for (var data in scrambledPattern)
Consumer<GameController>(builder: (context, gc, _) {
return Tile(
title: data,
isPuzzleComplete: gc.isPuzzleComplete,
);
})
],
),
Обновленное поведение
См. Примечание ниже и второй анимированный gif, чтобы сравнить и сопоставить изменения кода и новое поведение с исходным поведением выше.
После добавления UniqueKey как к целям, так и к плиткам - плитки неправильно отображаются в своем исходном местоположении, цели отображаются правильно.
Я хочу сделать следующее:
- Показывать плитки в упавшем положении на мишени.
- После другого события щелчка плитки и цели перерисовываются в исходное положение.
Примечание. Следующий код применен к виджетам "Плитка" и "Целевой объект".
key: (gc.isPuzzleComplete == true) ? UniqueKey() : null,
Решение
Благодаря напоминанию @ LoVe об использовании UniqueKey и обзору видео команды Flutter о ключах; Мне удалось решить проблему, условно добавив UniqueKey во включающую строку, содержащую как плитки, так и цель.
Изнутри каждого из этих виджетов я смог обновить внешний вид виджетов, используя (isSuccessful || widget.isPuzzleComplete)
. Мне также удалось удалить логику Consumer, связанную с захватом логики isPuzzleComplete из виджетов Tile / Target и из вызова setState.
В настоящее время поведение на 100% правильное.
//mod to parent row of Tiles
Row(
key: widget.isPuzzleComplete ? UniqueKey() : null,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
for (var data in scrambledPattern)
Tile( title: data,
isPuzzleComplete: widget.isPuzzleComplete,
),
],
),
//mod to draggable Tile
Draggable(
key: (widget.isPuzzleComplete == true) ? UniqueKey() : null,
data: widget.title,
child: (isSuccessful || widget.isPuzzleComplete)
? Container()
: TileContent(widget: widget),
//mod to drag target
return DragTarget(
key: UniqueKey(),
builder:
(context, List<String> candidateData, List<dynamic> rejectedData) {
return (widget.isPuzzleComplete || isSuccessful)
? Tile(
title: widget.data,
Ссылки
Видео Flutter Team по использованию ключей (ссылка)
1 ответ
Вам нужно будет использовать ключи, примерно так:
Tile( key:UniqueKey(), title: data, isPuzzleComplete: gc.isPuzzleComplete, ); }
Всякий раз, когда вы используете Row
или Column
или List
виджетов одного типа и вы часто их обновляете, то вы должны использовать Key
s, чтобы фреймворк знал, когда и когда не обновлять виджеты одного и того же типа,
подробнее об использовании ключей здесь