Часть серии Работа в разработке

В этой статье я расскажу, как пакет библиотеки фреймворка MVC позволяет пользователю «переключать» цветовую тему приложения Flutter одним щелчком мыши. Будет рассмотрен задействованный код, чтобы вы получили более полное представление о том, как настроена среда MVC, позволяющая использовать такие возможности в вашем собственном приложении Flutter.

Работа в процессе

Это часть серии статей A Work in Progress, посвященных развитию простого приложения ToDo, над которым я работаю, под названием WorkingMemory. Цель этой серии - документировать реализацию всех без исключения аспектов этого приложения, его конструкцию, а также использование пакета библиотеки фреймворка MVC, mvc_application.

Мне нравятся скриншоты. Щелкните для Gists.

Как всегда, я предпочитаю использовать скриншоты, а не суть, чтобы показать концепции, а не просто показывать код в своих статьях. Я считаю, что с ними легче работать и легче читать. Однако вы можете щелкнуть или коснуться их, чтобы увидеть код в сущности или в Github. По иронии судьбы, эту статью о мобильной разработке лучше читать на компьютере, чем на телефоне. Кроме того, мы в основном программируем на компьютере, а не на телефоне. Теперь.

Никаких движущихся изображений, никаких социальных сетей

В этой статье будут файлы gif, демонстрирующие аспекты рассматриваемой темы. Однако при чтении этой статьи на таких платформах, как Instagram, Facebook и т. Д., Просмотр таких файлов gif невозможен. Они могут выглядеть как статические изображения или просто пустые поля-заполнители. Помните об этом и, возможно, прочтите эту статью на medium.com.

Давай начнем.

По правде говоря, эта статья была вдохновлена ​​собственной статьей Ритеша Шармы Переключение тем в приложениях Flutter, как лиса! Его подход заключался в использовании собственного пакета библиотеки Норберта Козсира, dynamic_theme, чтобы помочь в динамическом переключении темы приложения, когда пользователь выбирает ее из раскрывающегося меню.

Это тема для упаковки

Я отметил, что для использования пакета библиотеки DynamicTheme он должен быть «корневым виджетом» для приложения (то есть первым объектом состояния, созданным для приложения). Реализация пакета библиотеки в качестве «корневого виджета» была практикой, которую я видел много раз раньше. Видите ли, это гарантирует, что вызовы функции пакета библиотеки setState () затем перестроят виджет MaterialApp или виджет CupertinoApp, который он оборачивает.

Например, ниже приведен снимок экрана с образцом кода Ритеша Шармы, на котором вы можете легко увидеть, что виджет MaterialApp действительно заключен в виджет DynamicTheme. Лично мне такой подход не нравится. Что делать, если у вас есть другие виджеты, которые должны обладать такой же способностью? Вы тогда бесконечно оборачиваете виджеты вокруг других виджетов? Откровенно говоря, было бы довольно уродливо, когда все, что нужно, - это готовый доступ к определенному объекту State.

Все, что вам нужно, - это доступ к объекту State, функция build () которого содержит виджет MaterialApp или CupertinoApp для вашего приложения. Когда у вас есть доступ к этому объекту State, у вас есть доступ к его функции setState (). Вы видите, что я имею в виду? Во всяком случае, есть другой подход. На мой взгляд, это более простой и понятный подход. Этот подход включает в себя готовую структуру, которая обеспечивает доступ к этому конкретному объекту State. Об этом и вся эта статья.

Установите свою тему

Прежде чем мы рассмотрим эту альтернативу, давайте продолжим этот подход, чтобы понять основы, связанные с достижением этой «динамической тематики». Итак, при изменении темы приложения все сводится к повторному вызову виджета MaterialApp или виджета CupertinoApp, но с другим значением именованного параметра theme. Это означает, что все сводится к вызову функции build () объекта State, которая содержит этот виджет MaterialApp или виджет CupertinoApp, и, конечно же, это достигается путем вызова метода setState этого объекта State. () функция. Следить так далеко?

Вы можете увидеть этот процесс, изображенный ниже, на скриншоте слева. Когда, например, выбрана новая тема, функция themedWidgetBuilder вызывается снова и возвращает виджет MaterialApp приложения, но не перед передачей этой переменной theme типа ThemeData . Это говорит о том, что функция themedWidgetBuilder вызывается снова и снова где-то в функции build () некоторого объекта State, и действительно, это тот случай, когда вы теперь смотрите ниже на снимок экрана справа. боковая сторона.

Обратите внимание, что «тема» передается из переменной частного экземпляра _data. Как вы знаете, функция build () вызывается снова и снова при каждом вызове функции setState () объекта State. Итак, вкратце, так достигается «динамическое изменение» темы приложения. Однако я объясню дальше и покажу вам, когда и где вызывается функция setState ().

Измените свою тему

В примере кода Ритеша Шармы, когда пользователь выбирает другой вариант темы из раскрывающегося меню, вызывается функция changeColor (). Именно там функция DynamicTheme.of () получает объект состояния DynamicThemeState. Затем вызывается функция setThemeData () этого объекта состояния. передача выбранного значения ThemeData. Все это отмечено первой красной стрелкой ниже.

Этот объект состояния, DynamicThemeState, находится в пакете библиотеки Норберта Козсира, dynamic_theme, и именно там функция setThemeData () назначает выбранные данные ThemeData частным переменная экземпляра называется _data. Помните эту переменную? Обратите внимание: это делается, когда он заключен в функцию setState () объекта State - вот и все. Это, в свою очередь, приведет к повторному вызову функции build () объекта State. Следовательно, виджет MaterialApp должен вызываться снова и передавать выбранный объект ThemeData в его именованный параметр theme. Таким образом, цветовая тема приложения изменяется динамически. Очень просто.

Итак, опять же, здесь важно вызвать функцию setState () для «корневого» объекта состояния, если и когда вы хотите изменить какой-либо аспект всего приложения.

Моя собственная тема

Мое приложение ToDo, WorkingMemory, имеет те же возможности. Он также может изменить свою тему ​​с помощью выбора из списка. Он также использует тот же базовый механизм, который снова и снова вызывает функцию build () объекта State, содержащую виджет MaterialApp или виджет CupertnoApp - в зависимости от запущенной платформы. Конечно, это приложение использует платформу MVC, поставляемую пакетом библиотеки, mvc_application, и поэтому именно эта структура предоставляет средства для выполнения такой операции.

Однако, в отличие от пакета DynamicTheme с его DynamicTheme.of (), выполняющим поиск в дереве виджетов определенного объекта состояния, структура всегда имеет свободный доступ к этому конкретному объекту состояния, представляющему интерес, - тому, который содержит 'root widget 'для любого приложения Flutter, созданного на основе фреймворка. На снимке экрана ниже показано, где была установлена ​​точка останова. Он останавливает выполнение платформы, в которой названному параметру theme для виджета MaterialApp назначается соответствующее значение ThemeData. В этом случае отображаемый здесь код находится в объекте State с именем AppView.

Это фреймворк, и поэтому, в отличие от примера кода Ритеша Шармы, приведенный выше снимок экрана выглядит более загруженным со всеми возможными параметрами, назначенными виджету MaterialApp. Обратите внимание, что это общая характеристика такого служебного класса - проверка большого количества параметров. Конечно, вы не обязаны указывать каждое значение параметра. Например, на скриншоте ниже моего приложения «ToDo» переданы только два значения параметров. Остальные проверяются значениями по умолчанию, как и любое другое приложение Flutter. Но я отвлекся.

Ваше тематическое меню

Как и в примере Шармы, в приложении WorkingMemory есть набор цветовых тем, доступных в раскрывающемся меню. На снимке экрана ниже слева показана функция initState () класса AppViewState. Обратите внимание, что AppView расширяет класс AppViewState в этой структуре, поэтому он является одним и тем же «корневым виджетом» для приложения Flutter. Ниже вы видите, как меню приложения инициализируется функцией initState () объекта State. Частью его инициализации является возврат приложения к последней выбранной цветовой теме, и, как и собственный пример кода Шармы, SharedPreferences используется для определения последней выбранной цветовой темы.

GIF-изображение, отображаемое ниже с правой стороны, показывает открытие палитры цветов из раскрывающегося меню и выбор новой цветовой темы.

Теперь, конечно, вы совсем не обязаны использовать это «Меню приложений» со своим следующим приложением Flutter. Однако, как и любой хороший фреймворк, он есть как вариант. Я использовал этот фреймворк в другом примере приложения под названием Bizaar, и в этом приложении вообще нет раскрывающегося меню. Однако это также меняет "тему" приложения в другом отношении. Вы можете узнать больше об этом в разделе TL; DR ниже, если хотите.

Какая у вас тема

Однако в этом приложении WorkingMemory я использую раскрывающееся меню по умолчанию, предлагаемое фреймворком. При этом вам будет доступна стандартная опция «О программе», а также эта симпатичная маленькая «палитра цветов» для изменения цветовой темы вашего приложения. Снимок экрана с функцией init () класса Appmenu показывает, как последняя выбранная цветовая тема переназначается приложению. Далее мы рассмотрим эту функцию onChange ().

Что вы предпочитаете

Сохранение ваших предпочтений - такая распространенная функция мобильных приложений, что возможность Общих предпочтений встроена в структуру. И в функции onChange (), и в получателе colorSwatch используется пакет библиотеки Prefs. Как и пример кода Ритеша Шармы, Prefs работает с плагином shared_preferences, чтобы сохранить и затем извлечь последнюю выбранную цветовую тему. В моем случае я написал пакет библиотеки Prefs, чтобы немного упростить работу с плагином.

Смена темы

Когда открывается палитра цветов и выбирается новый цвет, вызывается функция onChange (). Вся магия происходит в этой функции. На снимке экрана ниже функции показано, что выбранное значение ColorSwatch просто назначается свойству приложения themeData, а затем вызывается функция refresh () объекта State. Конечно, в базовом коде происходит немного больше, но это в значительной степени то, что касается этой стороны. Обратите внимание: если ваше приложение запущено на iOS, есть аналог «Купертино», который также получает выбранный цвет. Обратите внимание: точка останова ниже выделяет функцию Prefs.setInt (). Он записывает выбранное значение в общих настройках для следующего запуска приложения.

Вызов функции refresh () на снимке экрана выше в конечном итоге приводит к тому, что объект AppView State снова запускает свою функцию build (). Как вы знаете, это означает, что виджет MaterialApp создается снова, и, как вы видите ниже, когда точка останова остановила выполнение кода, названному параметру theme снова назначается свойство приложения App.themeData. Значение которого было изменено на скриншоте выше.

Варианты - Тема

Опять же, поскольку это фреймворк, вы заметите, что в этой точке останова есть еще два источника для именованного параметра, theme. Почему? Потому что! Вот почему. Фреймворк должен учитывать другие обстоятельства, которые могут определять способ создания темы приложения из одного конкретного приложения Flutter в другое.

Например, я мог бы так же легко назначить «тему» ​​классу «View» в моем приложении «ToDo». На снимке экрана ниже моего приложения ToDo, где когда-то было только два значения параметра, теперь есть третье значение параметра - тщательно продуманная тема теперь явно назначается именованному параметру theme.

Как видите, в этой структуре свойство theme имеет приоритет над свойством App, App.themeData. Конечно, как и у любого хорошего фреймворка, есть еще один вариант. Бог знает, как тема оформлена в некоторых приложениях. Возможно, приложение читает файл или базу данных, чтобы получить тему приложения. Может быть, Интернет замешан. Как бы то ни было, здесь на помощь приходит функция onTheme ().

На скриншоте ниже я представил цветовую тему «пурпурно-янтарный» с помощью функции onTheme () вместо того, чтобы явно передавать ее в именованный параметр theme. . Это просто, чтобы продемонстрировать, что у вас есть варианты в этом отношении. В этой функции может быть что угодно. Что я знаю?! Это твое приложение! Конечно, увидев приведенный выше код AppView, вы теперь понимаете, что каждый параметр, который в конечном итоге передается в виджет MaterialApp или виджет CupertinoApp, также имеет такие параметры. Отлично.

Итак, снова посмотрев на функцию build () для объекта AppView State, вы увидите, как оператор 'if null', ??, обеспечивает соблюдение этого порядка приоритет, оставляющий свойство App.themeData, предоставляя значение темы по умолчанию. Это свойство мы меняем в раскрывающемся меню приложения. Получи это сейчас?

Обновите свою тему

Вернемся к функции onChange () и посмотрим, как только свойству App.themeData будет присвоено новое значение, как снова вызывается и перестраивается виджет приложения MaterialApp. ? Что ж, как и в примере кода Ритеша Шармы, функция setState () для «корневого виджета» действительно вызывается. Однако это делается фреймворком без использования класса «оболочки». При первом запуске фреймворка готовый доступ к «корневому» объекту состояния становится доступным сразу же, если и когда разработчику это нужно. Давайте теперь покажем вам, как вызывается эта конкретная функция setState ().

Глядя на снимок экрана выше, обратите внимание на функцию обновить (). Теперь это не стандартная функция Flutter, используемая для перестройки интерфейса приложения. Скорее всего, вы знакомы с функцией setState (), которая вместо этого выполняет эту роль. Нет, эта функция, refresh (), находится во фреймворке и делает несколько больше, чем просто вызывает функцию setState () текущего объекта State.

Ниже приведен снимок экрана этой конкретной функции refresh (). Он вызывает функцию своего родительского класса refresh () с помощью команды super.refresh (), а затем вызывает собственное приложение refresh (). функция App.refresh (). Именно эта функция представляет особый интерес в данной статье, но продолжим.

Супер обновление

Именно в функции super.refresh () вы видите, что вызывается функция setState (). Функция refresh () находится в пакете библиотеки mvc_pattern, который является основным компонентом, который реализуется в шаблоне проектирования MVC во Flutter. Однако, если вы внимательно посмотрите на снимок экрана ниже, вы поймете, что вызываемая функция setState () также находится в этом пакете библиотеки MVC. Вы можете увидеть, что это перечислено над функцией refresh (), и именно эта функция, наконец, вызывает собственную функцию Flutter setState () для восстановления интерфейса. Это, конечно, если это разрешено с помощью переменной экземпляра _rebuildAllowed, но об этом в другой раз.

Обновление приложения

Теперь давайте быстро взглянем на функцию App.refresh (), где указанная выше точка останова была установлена ​​для остановки выполнения кода. Здесь вы легко увидите, что фреймворк действительно имеет ссылку на «корневой» объект состояния, называемый _vw. Именно эта переменная экземпляра _vw ссылается на объект State с именем AppView.

Конечно, разработчику не обязательно все это знать. Вам просто нужно знать, когда вы вызываете App.refresh (), он, в свою очередь, вызывает свою функцию refresh (), и вы можете сделать вывод из этого собственного Затем вызывается функция setState () - чтобы перестроить постоянный виджет MaterialApp. Тебе и этого не нужно знать. Просто знайте, что все приложение "обновляется", когда вы вызываете обновить ().

Давай оставим все как есть. Пометьте или разветвите приложение WorkingMemory и следуйте инструкциям, если хотите. Я буду работать над этим приложением в ближайшие месяцы. По мере развития событий я буду писать дополнительные статьи о конкретных аспектах этого приложения Flutter и о том, как оно использует шаблон проектирования MVC через пакет библиотеки mvc_application - все в попытке сделать поддерживаемое, но надежное приложение Flutter, пригодное для выпуска в производство.

Ваше здоровье.

TL;DR

Другой пример Bizaar

Есть еще один пример, который я хотел бы быстро рассмотреть вместе с вами, касающийся еще одного образца приложения под названием Bazaar. Это приложение подробно обсуждается в бесплатной статье Базар в MVC, но здесь я просто хотел изучить один его аспект, который также включает переключение его темы. В этом случае он просто позволяет пользователю изменить тему из светлого режима в темный и обратно.

Он реализован немного по-другому, но все же построен на той же платформе MVC, mvc_application, демонстрируя, что вы можете использовать эту структуру более чем одним способом. Разработчик может адаптировать свой код к конкретным потребностям - к определенному внешнему виду.

При переключении в темный режим и обратно буквально вызывается виджет Переключить для выполнения операции. Ниже приведен снимок экрана с этим кодом вместе с файлом gif, на котором само приложение переключается в темный режим и обратно.

Итак, как вы видите выше, когда пользователь нажимает на переключатель, вызывается функция setDarkMode (), за которой следует функция refresh (). Вы уже знакомы с функцией обновить (). Ниже первая стрелка указывает на процедуру настроек, используемую для «запоминания» текущего режима при следующем запуске приложения. Затем вызывается функция setTheme (), которая назначает соответствующую «тему» ​​переменной экземпляра themeData. Следить так далеко?

Обновите свою тему

Как и в предыдущих примерах, которые вы видели, затем вызывается функция setState (), которая заставляет виджет MaterialApp () «перестраивать». Следовательно, его именованный параметр, theme , присваивается последнее значение. Как вы знаете, в этой структуре функция refresh () в конечном итоге вызывает функцию setState ().

В этом приложении функция onTheme () используется для предоставления последнего значения. Ниже вы видите, что когда он вызывается, он обращается к контроллеру, ThemeChanger, чтобы получить это значение. Как-то отличается от моего приложения ToDo, верно? Однако это вполне приемлемо - фреймворк должен уметь «работать с ним», понимаете.

Если взглянуть на эту функцию getTheme (), вы увидите переменную экземпляра T hemeData. Посмотрите, как здесь все работает? Вы уже видели эту переменную экземпляра T hemeData раньше. Ему было присвоено новое значение в функции setTheme ().

Обновить состояние

Опять же, объект AppView является «корневым» объектом состояния для этой платформы. Следовательно, когда функция refresh () вызывается из любого места в приложении, AppView снова вызывает свою функцию build (), которая снова вызывает его виджет MaterialApp, а в В этом случае приложению назначается последняя тема.

Инициируйте свою тему

Обратите внимание: являясь расширением объекта StateMVC фреймворка, объект AppView вызывает свою функцию initState () и функцию initState () всех имеющихся у него объектов Controller. Это то, что вы видите на скриншоте ниже слева. Класс BaraarApp (расширяет AppView) имеет Контроллер BazaarApp, и ThemeChanger для работы с . Следовательно, когда объект State вызывает функцию initState () ThemeChanger, как вы видите на скриншоте ниже с правой стороны, это приложение получает тему для отображения, когда он запускается. Очень просто.

Сначала установите собственное состояние

Обратите внимание: вызов функции refresh () означает, что вы вызываете функцию setState () «текущего» объекта State, а затем setState () объекта AppView State. Это похоже на вызов двух функций на скриншоте ниже. А зачем вам это делать, спросите вы? Хорошо, я продемонстрирую.

Давайте немного изменим код и посмотрим, что произойдет, если вы просто вызовете функцию «Обновление приложения». Это незаметно, но вы заметите, что переключатель теперь не меняется в новом режиме ?! Видите ли, «текущий экран», если хотите, не был перестроен с помощью вызова функции setState () - только «корневой экран» из-за отсутствия лучших слов.

Давайте просто вызовем функцию setState () текущего объекта State. Конечно, теперь ты получил свой переключатель обратно. Он меняется при нажатии, но это все. Приложение вообще не меняет свою цветовую тему. Похоже, что в данном конкретном случае вам понадобятся и то, и другое, чтобы дать вам желаемый пользовательский интерфейс.

Смотрите ниже, они оба вернулись, и все в норме. Конечно, удаление этих строк сейчас и раскомментирование вызова функции, refresh (), также будет работать.

На этом пока все. Теперь у вас есть другое средство для доступа к «корневому виджету». Что еще более важно, чтобы получить доступ к объекту State корневого виджета. В конце концов, это тот объект, который содержит корневую функцию build ().

Ваше здоровье.

→ Другие рассказы Грега Перри