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 виджетов одного типа и вы часто их обновляете, то вы должны использовать Keys, чтобы фреймворк знал, когда и когда не обновлять виджеты одного и того же типа,

подробнее об использовании ключей здесь

Другие вопросы по тегам