Привет, если вы на пути изучения Flutter/Dart или вам просто интересно почитать про путь изучения, подписывайтесь на мой канал в telegram, буду рад вас видеть! А сегодня поговорим про разработку пользовательского интерфейса во Flutter!
Предыдущая статья: Работа с виджетами Flutter | 2 часть
Содержание
В этой статье мы переходим к теме создания пользовательских интерфейсов. Теперь, когда разобрали основы Flutter widgets (1 и 2 часть). Обсуждение сосредоточено на ключевых технических элементах проектирования красивого интерфейса. Мы поговорим, как:
Используйте шрифты для улучшения текстового интерфейса
Определите макет на экране для лучшего размещения
Укажите адрес хост-платформы
Используйте возможности Flutter для фундаментального улучшения ваших приложений
Понять, как обращаться к функциональным областям, зависящим от платформы, с помощью Dart SDK
Создать код, работающий с Flutter, для представления информации наиболее эффективным способом
Надеюсь, темы, приведенные в этой статье, позволят вам найти полезное и применить в своей разработке. Часто простые изменения, такие как выбор шрифтов и повышение отзывчивости вашего приложения, оказывают огромное влияние на общее впечатление. Темы, приведенные в этой главе, станут ключом к созданию расширяемых приложений, которые будут радовать ваших пользователей.
Использование пакета Google Fonts
Проблема
Вы хотите использовать внешние шрифты в приложении Flutter.
Решение
Flutter позволяет вам включать внешние шрифты как часть вашего приложения. Если вы не уверены, как добавить пакет шрифтов, смотрите инструкцию тут.
Вот как использовать Google Fonts для отображения пользовательского шрифта в приложении Flutter:
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Google Fonts Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
textTheme: TextTheme(
bodyText1:
GoogleFonts.aBeeZee(fontSize: 30, color: Colors.deepOrange),
bodyText2:
GoogleFonts.aBeeZee(fontSize: 30, color: Colors.white60))),
home: const MyHomePage(title: 'Flutter and Dart Cookbook'),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: Column(children: [
const Text('Yo MTV Raps'),
Text(
'Yo MTV Raps',
style: GoogleFonts.coiny(fontSize: 30, color: Colors.blueGrey),
),
Text(
'Yo MTV Raps',
style: GoogleFonts.actor(fontSize: 30, color: Colors.indigo),
),
]),
);
}
}
Обсуждение
В примере шрифты Google используются для демонстрации внедрения нового набора текста в ваше приложение.
В приложении используются два подхода к настройке шрифта Google. Во-первых, textTheme устанавливается как часть общей темы приложения. Используйте этот подход, если вы хотите установить значение по умолчанию для своего приложения. Распространенный вопрос заключается в том, почему стиль bodyText2 имеет приоритет. Причина в том, что он установлен в качестве стиля текста по умолчанию для приложений, связанных с материалами.
Второй подход применяет шрифт Google непосредственно к текстовому виджету. Здесь мы можем индивидуально настроить шрифт по мере необходимости.
Использование RichText
Проблема
Вы хотите иметь больше контроля над текстом, отображаемым на экране.
Решение
Используйте виджет RichText для настройки текста, отображаемого на экране.
Вот как использовать RichText для написания пользовательского текста в приложении Flutter:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
const title = 'RichText Demo';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
body: const MyRichText(),
),
);
}
}
double screenHeight = 0.0;
class MyRichText extends StatelessWidget {
const MyRichText({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
screenHeight = MediaQuery.of(context).size.height / 3;
return RichText(
text: const TextSpan(
children: [
TextSpan(
text: 'Hello',
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24),
),
TextSpan(
text: 'Luxembourg',
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 32, color: Colors.grey),
),
],
),
);
}
}
Обсуждение
В примере кода RichText используется для отображения информации с использованием двух различных стилей. В первом случае к тексту применен жирный шрифт. Во втором случае цвет текста установлен на серый. В примере кода используется медиа-запрос, который обсудим чуть ниже.
Если вам требуется больший контроль над тем, как будет отображаться текст приложения, используйте RichText. Это обеспечит больший контроль над различными стилями, которые могут быть применены, сохраняя при этом функциональность, связанную с текстовым виджетом.
Идентификация принимающей платформы
Проблема
Вы хотите проверить, на какой платформе запускается приложение, чтобы учесть целевую платформу.
Решение
В некоторых случаях вы можете захотеть узнать конкретную хост-платформу, на которой запущено приложение. Это может быть полезно, если вам нужно соблюдать пользовательские критерии, применяемые в приложении, таком как Android или iOS.
Вот как проверить, на какой хост-платформе запущено приложение:
import 'package:flutter/material.dart';
import 'dart:io' show Platform;
import 'package:flutter/foundation.dart' show kIsWeb;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
const title = 'Platform demo';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
body: const MyPlatformWidget(),
),
);
}
}
class MyPlatformWidget extends StatelessWidget {
const MyPlatformWidget({Key? key}) : super(key: key);
bool get isMobileDevice => !kIsWeb && (Platform.isIOS || Platform.isAndroid);
bool get isDesktopDevice => !kIsWeb && (Platform.isMacOS || Platform.isWindows || Platform.isLinux);
bool get isMobileDeviceOrWeb => kIsWeb || isMobileDevice;
bool get isDesktopDeviceOrWeb => kIsWeb || isDesktopDevice;
bool get isAndroid => !kIsWeb && Platform.isAndroid;
bool get isFuchsia => !kIsWeb && Platform.isFuchsia;
bool get isIOS => !kIsWeb && Platform.isIOS;
bool get isLinux => !kIsWeb && Platform.isLinux;
bool get isMacOS => !kIsWeb && Platform.isMacOS;
bool get isWindows => !kIsWeb && Platform.isWindows;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text(
'isMobileDeviceOrWeb: $isMobileDeviceOrWeb',
style: const TextStyle(fontSize: 20, color: Colors.grey),
),
Text(
'isDesktopDeviceOrWeb: $isDesktopDeviceOrWeb',
style: const TextStyle(fontSize: 20, color: Colors.grey),
),
],
);
}
}
Обсуждение
В примере кода платформа Dart запрашивает для ссылки на заданные значения, указывающие тип платформы, на которой выполняется приложение. Используйте dart.io для доступа к классу platform, который имеет такие свойства, как IsMobileDevice или isLinux.
Если вам необходимо получить доступ к этой информации в вашем приложении, импортируйте пакет dart.io, чтобы можно было получить доступ к необходимым свойствам платформы. Когда вам нужно учесть конструктивные ограничения определенного типа устройства или понять, где развертывается ваше приложение, этот пакет может быть особенно полезен.
Кроме того, при тестировании полезно понимать, на какой хост-платформе запущено приложение. Flutter позволяет определить хост-платформу, используя предопределенные константы платформы. В документации информация, относящаяся к хост-платформе, находится в разделе сегментация устройств и ссылается на API платформы.
API платформы поддерживает основные платформы (например, Android, Fuchsia, iOS, Linux, macOS и Windows). В рамках примера каждый тест для конкретной конфигурации может использоваться для идентификации хост-платформы. Кроме того, существует отдельная настройка (например, kIsWeb) для веб-приложений.
Использование виджета Placeholder
Проблема
Вы хотите создать пользовательский интерфейс, когда доступны не все графические ресурсы.
Решение
Используйте виджет Placeholder для представления ресурсов интерфейса, которые еще предстоит добавить в приложение.
Вот пример, в котором мы возвращаемся к Expanded коду виджета. Вместо добавления необходимых цветов и текста используется виджет Placeholder:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
const title = 'Expanded Widget';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
body: const MyExpandedWidget(),
),
);
}
}
class MyExpandedWidget extends StatelessWidget {
const MyExpandedWidget();
@override
Widget build(BuildContext context) {
return Column(
children: const [
Expanded(
child: Placeholder(
fallbackHeight: 400, strokeWidth: 10, color: Colors.red),
),
Expanded(
child: Placeholder(
fallbackHeight: 400, strokeWidth: 10, color: Colors.white),
),
Expanded(
child: Placeholder(
fallbackHeight: 400, strokeWidth: 10, color: Colors.blue),
),
],
);
}
}
Обсуждение
В примере кода виджет Placeholder используется для резервирования части отображения. Резервирование позволяет продолжить разработку там, где такие артефакты, как изображения и текст, могут быть недоступны немедленно. Результатом добавления заполнителя является поле с перечеркнутым контуром, в котором должно быть отображено отсутствующее содержимое, как показано на рисунке 10-1.
Пример кода иллюстрирует, как можно использовать placeholder для заполнения пространства, которое в противном случае использовалось бы при наличии необходимой информации. Если вам нужно выделить визуальное пространство без обязательного добавления вспомогательного ресурса, виджет placeholder может оказаться очень полезным.
Placeholder поддерживает дополнительные свойства, такие как высота виджета (fallbackHeight) и ширина (fallbackWidth), позволяющие устройствам корректно масштабироваться. Кроме того, виджет также поддерживает цвет (color) и ширину линии (strokeWidth), чтобы обеспечить дополнительную гибкость на этапе проектирования при создании приложения.
Использование LayoutBuilder
Проблема
Вы хотите составить макет на основе контекста устройства, например, портретного или сенсорного, адаптивным образом.
Решение
Используйте виджет LayoutBuilder для автоматической обработки требований к адаптивному макету экрана.
Здесь мы возвращаемся к нашему примеру флаг из RichText и отображаем ряд столбцов, относящихся к ширине отображения экрана:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'LayoutBuilder';
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatelessWidget(),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('LayoutBuilder Example')),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// Restrict based on Width
if (constraints.maxWidth > 800) {
return _buildTripleContainers();
} else if (constraints.maxWidth > 600 &&
constraints.maxWidth <= 800) {
return _buildDoubleContainers();
} else {
return _buildSingleContainer();
}
},
),
);
}
Widget _buildSingleContainer() {
return Center(
child: Container(
height: 400.0,
width: 100.0,
color: Colors.red,
),
);
}
Widget _buildDoubleContainers() {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
height: 400.0,
width: 100.0,
color: Colors.yellow,
),
Container(
height: 400.0,
width: 100.0,
color: Colors.yellow,
),
],
),
);
}
Widget _buildTripleContainers() {
return Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
height: 400.0,
width: 100.0,
color: Colors.green,
),
Container(
height: 400.0,
width: 100.0,
color: Colors.green,
),
Container(
height: 400.0,
width: 100.0,
color: Colors.green,
),
],
),
);
}
}
Обсуждение
В примере кода виджет LayoutBuilder используется для возврата определенного вида на основе доступных размеров экрана. Применяемое ограничение основано на ширине и будет отслеживать размер доступного окна, возвращая определенный вид при выполнении заданного критерия. Если ширина окна превышает 800, вы увидите три зеленых вертикальных прямоугольника. Когда ширина окна находится в диапазоне от 600 до 800, вы увидите два желтых вертикальных прямоугольника. В противном случае вы увидите один красный вертикальный прямоугольник. Три возможных вида показаны на рис. 10-2.
LayoutBuilder позволяет разработчикам адаптировать макет своего приложения и может использоваться в ситуациях, когда вам нужно понять, как компоновать экран на основе доступных экранных ограничений.
Виджет LayoutBuilder предоставляет адаптивный интерфейс; наиболее распространенным сценарием для этого было бы, когда приложение запускается на разных устройствах. Поскольку устройства имеют разные размеры, возможность динамического отображения контента в рамках определенных ограничений по размеру очень удобна. Преимущество, связанное с этим, выходит за рамки размеров экрана и включает в себя запрашиваемое оборудование, такое как тип ввода (мышь и клавиатура), визуальная плотность (веб по сравнению с мобильным устройством) и тип выбора.
Для каждого вызова виджета LayoutBuilder вы можете запрашивать размеры экрана с помощью класса BoxConstraints, связанного с контекстом просмотра. Типичный вариант использования этой функциональности - использовать структуру ограничений для определения пороговых значений экрана. Эти пороговые значения могут быть использованы для эффективного упорядочивания доступных размеров. Используйте LayoutBuilder, чтобы включить интеллектуальную компоновку отображаемых экранов. Пример с использованием размеров устройства является одним из многих способов использования этого виджета.
Сравните виджет LayoutBuilder с классом MediaQuery, который учитывает размер и ориентацию приложения. Класс MediaQuery предоставляет информацию о быстродействии (т.е. настраивается на доступные настройки устройства), в то время как LayoutBuilder предоставляет виджет адаптивного макета для компоновки экранных элементов (т.е. тип ввода, визуальная плотность и тип выделения).
Доступ к размерам экрана с помощью MediaQuery
Проблема
Вы хотите получить доступ к размерам устройства.
Решение
Используйте класс MediaQuery для поиска информации, относящейся к главному устройству. Класс вернет множество свойств, таких как соотношение сторон, отступы и ориентация.
Вот пример, в котором мы используем класс MediaQuery для возврата информации о главном устройстве и вывода информации о текущих настройках среды:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
const title = 'MediaQuery demo';
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: const Text(title),
),
body: const MyMediaQueryWidget(),
),
);
}
}
class MyMediaQueryWidget extends StatelessWidget {
const MyMediaQueryWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
var screenSize = MediaQuery.of(context).size;
if (screenSize.width > 600) {
// Two Column
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'You can Fit Two columns here!',
style: TextStyle(fontSize: 30, color: Colors.grey),
),
const SizedBox(height: 10.0),
Text(
'Screen Width: ${MediaQuery.of(context).size.width}',
style: const TextStyle(fontSize: 20, color: Colors.grey),
),
Text(
'Screen Height: ${MediaQuery.of(context).size.height}',
style: const TextStyle(fontSize: 20, color: Colors.grey),
),
Text(
'Aspect Ratio: ${MediaQuery.of(context).size.aspectRatio}',
style: const TextStyle(fontSize: 20, color: Colors.grey),
),
Text(
'Orientation: ${MediaQuery.of(context).orientation}',
style: const TextStyle(fontSize: 20, color: Colors.grey),
),
],
);
} else {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'You can Fit One column here!',
style: TextStyle(fontSize: 30, color: Colors.grey),
),
const SizedBox(height: 10.0),
Text(
'Screen Width: ${MediaQuery.of(context).size.width}',
style: const TextStyle(fontSize: 20, color: Colors.grey),
),
Text(
'Screen Height: ${MediaQuery.of(context).size.height}',
style: const TextStyle(fontSize: 20, color: Colors.grey),
),
Text(
'Aspect Ratio: ${MediaQuery.of(context).size.aspectRatio}',
style: const TextStyle(fontSize: 20, color: Colors.grey),
),
Text(
'Orientation: ${MediaQuery.of(context).orientation}',
style: const TextStyle(fontSize: 20, color: Colors.grey),
),
],
);
}
}
}
Обсуждение
Как показано на рисунке 10-3, в примере кода MediaQuery используется для возврата контекстно-зависимой информации, относящейся к конфигурации устройства. Контекст предоставляет размер экрана, и это используется для отображения другой композиции экрана в зависимости от возвращаемой ширины. MediaQuery всегда доступен, так что это очень удобный способ собрать информацию о том, как получить доступ к различным настройкам.
Вызов MediaQuery.of, согласно приведенному примеру, будет означать перестройку дерева виджетов при изменении свойств. Поэтому при использовании этого класса помните о влиянии на производительность, вызванном автоматической перестройкой всякий раз, когда происходят изменения (например, поворот устройства, обновление размера).
В документации по Flutter ключевым отличием является разница между отзывчивостью и адаптивностью. Суть в том, что отзывчивость зависит от доступного размера экрана. Если вы собираетесь рассматривать различные типы устройств, то, скорее всего, захотите включить адаптивность то нужен LayoutBuilder, например, мышь, клавиатуру, стратегию выбора компонентов.
Если вы когда-либо занимались веб-разработкой, вы, скорее всего, знакомы с MediaQuery. Как правило, этот класс может использоваться для предоставления свойств, используемых для создания адаптивного интерфейса. В частности, метод MediaQuery.of(context) может использоваться для доступа к такой информации, как настройка ориентации устройства, состояние коэффициента масштабирования текста, применяемые отступы, ограничения, применяемые к анимации, и яркость платформы, указывающая уровень контрастности.