Перетаскивание заголовка для HorizontalDataTable

В последнее время я постепенно обновляю общие примеры при использовании пакетов horizontal_data_table. Одной из популярных тем является возможность изменения порядка колонок. Эта функция действительно проста, если работать с виджетом flutter Draggable и DropTarget. Ниже описаны шаги, которые я выполнял на примере SimpleTablePage и надеюсь, что это также может быть полезно в качестве примера использования Draggable и DropTarget.

Весь класс можно проверить в репозитории GitHub.

Вот что показано в конце статьи:

Давайте начнем!

Сначала извлеките важную информацию из каждого столбца в закрытый список. Я создал класс данных UserColumnInfo только для хранения названия и ширины заголовка.

late List<UserColumnInfo> _colInfos;

@override
  void initState() {    
    super.initState();
    widget.user.initData(100);
    _colInfos = [
      const UserColumnInfo('Name', 100),
      const UserColumnInfo('Status', 100),
      const UserColumnInfo('Phone', 200),
      const UserColumnInfo('Register', 100),
      const UserColumnInfo('Termination', 200),
    ];
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Далее мы начинаем работать над заголовком. Если вы проследите до функции _getTitleWidget, то увидите, что виджеты заголовка в основном одинаковы, единственное отличие — их ширина.

List<Widget> _getTitleWidget() {
    return [
      _getTitleItemWidget('Name', 100),
      _getTitleItemWidget('Status', 100),
      _getTitleItemWidget('Phone', 200),
      _getTitleItemWidget('Register', 100),
      _getTitleItemWidget('Termination', 200),
    ];
  }
Вход в полноэкранный режим Выход из полноэкранного режима

Затем мы можем легко применить функцию _colInfos для создания списка виджетов, вот так:

List<Widget> _getTitleWidget() {
    return _colInfos.map((e) => _getTitleItemWidget(e.name, e.width)).toList();
}
Войти в полноэкранный режим Выйти из полноэкранного режима

В конце концов, все готово. Мы можем приступить к реализации перетаскивания.

Поскольку мы будем работать с Draggable и DropTarget, если вы не знакомы с этими двумя виджетами, рекомендуем вам потратить несколько минут на просмотр этого видео с сайта flutter.dev:

Что мне нужно сделать, это подготовить заголовок виджета Draggable, чтобы люди могли перетаскивать его, и DropTarget для других заголовков, на которые они будут падать. И это все! Более подробно я объясню ниже.

List<Widget> _getTitleWidget() {
    return _colInfos
        .map((e) => DragTarget(
              builder: (context, candidateData, rejectedData) {
                return Draggable<String>(
                  data: e.name,
                  feedback:
                      Material(child: _getTitleItemWidget(e.name, e.width)),
                  child: _getTitleItemWidget(e.name, e.width),
                );
              },
              onWillAccept: (value) {
                return value != e.name;
              },
              onAccept: (value) {
                int oldIndex =
                    _colInfos.indexWhere((element) => element.name == value);
                int newIndex =
                    _colInfos.indexWhere((element) => element.name == e.name);
                UserColumnInfo temp = _colInfos.removeAt(oldIndex);
                _colInfos.insert(newIndex, temp);
                setState(() {});
              },
            ))
        .toList();
  }
Вход в полноэкранный режим Выход из полноэкранного режима

builder предназначен для создания виджета, который отображает DragTarget. Я создаю Draggable внутри, чтобы дочерний элемент можно было перетаскивать в другое место. Виджет feedback обернут виджетом Material, потому что виджет feedback не наследует родительскую тему по умолчанию. Если вы хотите, чтобы он принял тот же пользовательский интерфейс, как выглядит существующий заголовок. Вам нужно, чтобы дочерний элемент feedback был обернут виджетом Theme. В данном случае используется тема Materail и поэтому я просто использую виджет Material для решения этой проблемы.

builder: (context, candidateData, rejectedData) {
  return Draggable<String>(
    data: e.name,
    feedback:
        Material(child: _getTitleItemWidget(e.name, e.width)),
    child: _getTitleItemWidget(e.name, e.width),
  );
}
Вход в полноэкранный режим Выход из полноэкранного режима

onWillAccept указывает, какое значение разрешено принимать. Так как при переходе разрешается принимать только заголовок, отличный от заголовка существующей позиции, значение устанавливается не равным текущему имени заголовка.

onWillAccept: (value) {
  return value != e.name;
},
Вход в полноэкранный режим Выход из полноэкранного режима

onAccept обрабатывает изменения, когда падение принято. Сначала я узнаю старый и новый индекс столбца. А затем просто удаляю и снова вставляю столбец.

onAccept: (value) {
  int oldIndex =
      _colInfos.indexWhere((element) => element.name == value);
  int newIndex =
      _colInfos.indexWhere((element) => element.name == e.name);
  UserColumnInfo temp = _colInfos.removeAt(oldIndex);
  _colInfos.insert(newIndex, temp);
  setState(() {});
}
Вход в полноэкранный режим Выход из полноэкранного режима

В то время как заголовочная часть завершена, основная часть должна следовать за изменением колонок. Я использую аналогичный подход для основной части. Сначала я извлекаю виджеты ячеек таблицы. Поскольку обычно существует два типа ячеек, один из которых — обычный текст, а другой — иконка. У меня есть эти две функции:

Widget _generateGeneralColumnCell(
      BuildContext context, int rowIndex, int colIndex) {
    return Container(
      width: _colInfos[colIndex].width,
      height: 52,
      padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
      alignment: Alignment.centerLeft,
      child: Text(widget.user.userInfo[rowIndex].get(_colInfos[colIndex].name)),
    );
  }

  Widget _generateIconColumnCell(
      BuildContext context, int rowIndex, int colIndex) {
    return Container(
      width: 100,
      height: 52,
      padding: const EdgeInsets.fromLTRB(5, 0, 0, 0),
      alignment: Alignment.centerLeft,
      child: Row(
        children: <Widget>[
          Icon(
              widget.user.userInfo[rowIndex].status
                  ? Icons.notifications_off
                  : Icons.notifications_active,
              color: widget.user.userInfo[rowIndex].status
                  ? Colors.red
                  : Colors.green),
          Text(widget.user.userInfo[rowIndex].status ? 'Disabled' : 'Active')
        ],
      ),
    );
  }
Войти в полноэкранный режим Выход из полноэкранного режима

Вы можете заметить, что это get(_colInfos[_colIndex].name) для получения поля UserInfo. Поскольку заголовок динамически изменяется, поле не может быть жестко закодировано. Я добавил функцию get в класс UserInfo для получения значения поля по имени заголовка.

dynamic get(String fieldName) {
  if (fieldName == 'Name') {
    return name;
  } else if (fieldName == 'Status') {
    return status;
  } else if (fieldName == 'Phone') {
    return phone;
  } else if (fieldName == 'Register') {
    return registerDate;
  } else if (fieldName == 'Termination') {
    return terminationDate;
  }
  throw Exception('Invalid field name');
}
Вход в полноэкранный режим Выход из полноэкранного режима

Левая и правая стороны HorizontalDataTable также будут изменены, как и заголовок:

Widget _generateFirstColumnRow(BuildContext context, int rowIndex) {
  if (_colInfos.first.name == 'Status') {
    return _generateIconColumnCell(context, rowIndex, 0);
  } else {
    return _generateGeneralColumnCell(context, rowIndex, 0);
  }
}

Widget _generateRightHandSideColumnRow(BuildContext context, int rowIndex) {
  return Row(
    children: _colInfos.sublist(1).map((e) {
      if (e.name == 'Status') {
        return _generateIconColumnCell(
            context, rowIndex, _colInfos.indexOf(e));
      } else {
        return _generateGeneralColumnCell(
            context, rowIndex, _colInfos.indexOf(e));
      }
    }).toList(),
  );
}
Войти в полноэкранный режим Выход из полноэкранного режима

Наконец, поскольку столбцы будут меняться в разном порядке, ширина столбцов также изменится. Общая ширина колонки с фиксированной стороной и двунаправленной стороной должна быть обновлена следующим образом:

double get _sumOfRightColumnWidth {
  return _colInfos
      .sublist(1)
      .map((e) => e.width)
      .fold(0, (previousValue, element) => previousValue + element);
}

HorizontalDataTable(
  leftHandSideColumnWidth: _colInfos.first.width,
  rightHandSideColumnWidth: _sumOfRightColumnWidth,
    ...
)
Войти в полноэкранный режим Выйти из полноэкранного режима

Вот как это выглядит сейчас:

Поддержите меня, если вам нравится этот контент🍖

Подключайтесь🍻

Оцените статью
devanswers.ru
Добавить комментарий