Всем доброго дня!
Меня зовут Алексей, я основатель и frontend разработчик системы автоматизации работы управляющих компаний «Оператор 18».
Я написал ее с использованием языка Dart и фреймворка Flutter, что позволило мне использовать единую кодовую базу сразу для веб приложения и мобильных платформ iOS и Android.
Как то я уже писал статью об Операторе18. Но тогда это была первая версия проекта, я собирал мнения, пробовал оценить рынок, выбрать вариант монетизации и т. д.
Сейчас я занимаюсь тем что называется — переписыванием проекта с нуля. Хочу, учитывая ошибки прошлого, переписать код, применить архитектуру, структурировать проект лучше. В общем вложиться в развитие на будущее.
В этой и последующих статьях, я хочу поделиться своим опытом о том, с какими сложностями сталкиваюсь при переписывании с нуля и как решаю их. Хорошо если кто нибудь найдёт в моих статьях что-то полезное для себя.
И так, первая версия проекта есть, но выглядит так себе. Точнее сказать — нет какого то дизайна, собирал интерфейс из штатных виджетов, максимум меняя цвет и размер шрифта. В общем — не заморачивался особо. Архитектуры тоже нет, но об этом в следующих статьях.
Для второй версии я решил заказать дизайн.
У меня не было особых пожеланий или требований. Мне хотелось видеть простой и лаконичный дизайн, функциональный, если можно так сказать.
Но, как известно, дизайн — это создание виджетов, нестандартных, кастомных. И на этом моменте я оказался перед выбором: писать свои виджеты или искать что-то работающее с нужным мне функционалом в пабе.
Изначально я был уверен что буду сам писать виджеты, прямо с нуля. Я считал что не стоит держать в проекте много сторонних зависимостей! Можешь если сам написать что-то — пиши!
Вот, например, окно входа в систему в новом дизайне выглядит так:
И я, решив прислушаться к себе, не стал искать готовых решений, а написал свой виджет:
class LoginDropdown extends StatefulWidget {
final Function(String) onUserRoleChanged;
const LoginDropdown({
required this.onUserRoleChanged,
});
@override
State<LoginDropdown> createState() => _LoginDropdownState();
}
class _LoginDropdownState extends State<LoginDropdown> {
bool isShowMenu = false;
String currentRole = UserRole.mcOperator;
Color roleDropdownButtonColor = AppColors.gray_3;
@override
Widget build(
BuildContext context,
) =>
MouseRegion(
onEnter: (_) =>
setState(() => roleDropdownButtonColor = AppColors.gray_1),
onExit: (_) =>
setState(() => roleDropdownButtonColor = AppColors.gray_3),
child: GestureDetector(
onTap: () {
setState(() {
// ignore: avoid_bool_literals_in_conditional_expressions
isShowMenu = isShowMenu ? false : true;
});
},
child: Stack(
children: [
Container(
height: 56,
width: 418,
decoration: BoxDecoration(
color: roleDropdownButtonColor,
borderRadius: const BorderRadius.all(
Radius.circular(12),
),
),
child: Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
currentRole,
style: AppFonsts.dropDown_1,
),
Image.asset(
'icons/dropdown_arrow.png',
height: 20,
width: 20,
),
],
),
),
),
if (isShowMenu)
Padding(
// 56 - is height of first container,
// 8 - is constraint between containers
padding: const EdgeInsets.only(top: 56 + 8),
child: Container(
height: 166,
width: 418,
decoration: const BoxDecoration(
boxShadow: [
// BoxShadow setup found here:
// https://devsheet.com/code-snippet/add-box-shadow-to-container-in-flutter/
BoxShadow(
color: AppColors.gray_6,
blurRadius: 90,
offset: Offset(0, 20),
)
],
color: AppColors.white,
borderRadius: BorderRadius.all(
Radius.circular(12),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 14),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
for (var role in UserRole.list)
LoginDropdownItem(
onTap: () {
setState(() {
isShowMenu = false;
currentRole = role;
widget.onUserRoleChanged(role);
});
},
userRole: role,
),
],
),
),
),
),
],
),
),
);
}
Для меня «свой виджет» — это не просто кнопка, например, которой поменяли цвет и скруглили углы. «Свой виджет» — это виджет при создании которого нужно описывать его поведение и реакцию на действия пользователя.
Получилось хорошо, возможно не хватает какой нибудь анимации, но в целом — я остался доволен!
Мне как новичку, не имеющему большого опыта создания своих виджетов, этот процесс показался затратным по времени, потому что необходимо скомпоновать виджеты, определить все методы и параметры, подумать над логикой поведения. Я понял что это, в целом, является излишним.
После этого я подумал что совсем уж кастомные решения можно сделать при необходимости. Но в данном случае получилось так, что я изобрёл колесо.
Я оставил этот виджет в проекте, не стал заменять на коробочное решение. Хотя в другом месте подобный виджет выглядит уже следующий образом:
// Inside a Row widget
DropdownButtonHideUnderline(
child: DropdownButton2(
icon: const SizedBox(),
dropdownWidth: 418.w,
dropdownMaxHeight: 233.h,
dropdownDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(16.r),
),
hint: Row(
children: [
Text(
AppString.more,
style: AppFonts.menuUnselected,
),
SizedBox(
width: 5.w,
),
Image.asset(
'icons/dropdown_arrow.png',
height: 20.h,
width: 20.w,
),
],
),
items: items.map(
(item) => DropdownMenuItem<String>(
value: item,
child: Text(
item,
style: AppFonts.dropDownBlack,
),
),
).toList(),
value: selectedValue,
onChanged: widget.onLogSelected,
itemHeight: 35.h,
itemPadding: EdgeInsets.only(
left: 28.w,
),
),
),
// Inside a Row widget
Спасибо за прочтение! Буду благодарен за критику/советы/иные комментарии.
Комментарии (6)
ookami_kb
31.08.2022 20:25Мне как новичку, не имеющему большого опыта создания своих виджетов, этот процесс показался затратным по времени, потому что необходимо скомпоновать виджеты, определить все методы и параметры, подумать над логикой поведения. Я понял что это, в целом, является излишним.
А откуда возьмется опыт, если не создавать свои виджеты? Понятно, что если опыта нет, то будет долго и плохо. Например, конкретно в этом случае можно было взять стандартный DrodownButton и кастомизовать его, а не изобретать странный велосипед с MouseRegion и Stack.
А уж тащить ради этого левый пакет, ну это уже какой-то npm-синдром.
Вся статья, это вопрос – писать виджет самому или искать готовый пакет? Ну тогда ответ: it depends. Но в 90% случаев, лучше писать самому. Сначала – для обучения, потом – чтобы не скатываться в dependency hell там, где это совершенно не нужно.
kharitonovAL Автор
31.08.2022 20:54Спасибо за комментарий!
На сколько помню, мне не удалось кастомизировать так как надо штатный DropdownButton. Но допускаю что не досконально изучил возможности этого виджета и работу с ним.
MouseRegion нужен для изменения цвета фона строки над которым находится указатель, ведь весь UI конкретного модуля - исключительно для web application. Хотя, теперь можно наверно и в нативку винды завернуть.
Stack нужен для того, чтобы элементы не сдвигались. Если взять Column, то при нажатии на выпадающий элемент остальные просто сдвинутся вниз на соответствующую высоту. А Stack как раз позволяет сделать этакий overlap.
Может быть для этого всего можно было использовать какие то другие виджеты, но у меня получилось то что я хотел :)
ookami_kb
31.08.2022 22:05+1Stack нужен для того, чтобы элементы не сдвигались. Если взять Column, то при нажатии на выпадающий элемент остальные просто сдвинутся вниз на соответствующую высоту. А Stack как раз позволяет сделать этакий overlap.
Для этого в том же DropdownButton используется Overlay, он даст ряд преимуществ.
Может быть для этого всего можно было использовать какие то другие виджеты, но у меня получилось то что я хотел :)
Можно было, но для этого нужен опыт, который нужно нарабатывать написанием виджетов, как я уже упоминал выше. А подключение сторонних пакетов этого опыта не даст, к тому же, чаще всего UI-пакеты (из моего опыта) довольно сомнительного качества.
Опять же, говоря о приложении в целом, одно дело, если вы берете готовый UI-kit с возможностью кастомизации, и используете его (почти) целиком (например, https://pub.dev/packages/macos_ui) – тут подключение библиотеки оправдано. А другое дело, когда на каждый компонент будет отдельный пакет, и потом все это кастомизовать и поддерживать... Флаттер – это уже, в первую очередь, UI-фреймворк, причем с довольно хорошо продуманным API и хорошей библиотекой стандартных компонентов. Советую больше времени потратить на изучение самого фреймворка и стандартных виджетов, чем на поиск и кастомизацию сторонних пакетов.
kharitonovAL Автор
01.09.2022 05:04Я в вообще и сам не приветствую множество сторонних зависимостей. Большое спасибо за ценный совет! Буду изучать!
Haid00k
Интересно, пишите ещё.
Вы самостоятельно пишете «с нуля» всю CRM?
kharitonovAL Автор
Добрый день! Спасибо за ваш комментарий.
Да, пишу с нуля. Вернее написал первую версию с нуля, теперь переписываю её снова с нуля, но уже с учётом архитектуры и возможного масштабирования.