Евгений Колесников před 3 roky
rodič
revize
d9f23c56c7
9 změnil soubory, kde provedl 426 přidání a 3 odebrání
  1. 161 0
      articles/android_bottom_navigation.md
  2. 258 0
      articles/cs_layout2.md
  3. 4 2
      articles/cs_mysql_connection3.md
  4. binární
      img/android001.png
  5. binární
      img/android002.png
  6. binární
      img/android003.png
  7. binární
      img/cs005.png
  8. binární
      img/cs006.png
  9. 3 1
      readme.md

+ 161 - 0
articles/android_bottom_navigation.md

@@ -0,0 +1,161 @@
+# Навигация в приложении. Начало работы с Navigation Architecture Component. Знакомство с BottomNavigationView.
+
+>Содрано [отсюда](https://www.fandroid.info/17-android-bottom-navigation/)
+
+На этой лекции познакомимся с нижней панелью навигации BottomNavigationView, которая позволяет переходить между экранами – пунктами назначения навигации, а также наглядно информирует пользователя о том, на каком экране он находится. Разберемся, как добавить Bottom Navigation в андроид приложение и как добавить в BottomNavigationView новые фрагменты.
+
+## Содержание
+
+* [Navigation Architecture Component](#navigation-architecture-component)
+* [Создаем проект](#создаем-проект)
+* [Неоходимые библиотеки](#неоходимые-библиотеки)
+* [Граф навигации](#граф-навигации)
+* Добавление экранов
+* Хост навигации
+* BottomNavigationView
+* Добавление пунктов меню
+* Добаление идентификаторов в контроллер навигации
+* Рефакторинг
+
+## Navigation Architecture Component
+
+**Navigation Architecture Component** это библиотека, которая позволяет пользователям перемещаться между различными частями контента в вашем приложении. Компонент навигации входит в набор компонентов Android Jetpack и помогает реализовать навигацию, от простых нажатий кнопок до более сложных шаблонов, таких как панели приложений (appbars) и панель навигации (navigation drawer). Компонент навигации также обеспечивает согласованное и предсказуемое взаимодействие с пользователем.
+
+Navigation Architecture Component упрощает осуществление навигации, а также помогает визуализировать **navigation flow** вашего приложения. Библиотека предоставляет ряд преимуществ, в том числе:
+
+* Автоматическая обработка транзакций фрагментов
+* Корректная обработка кнопок «Вверх» и «Назад» по умолчанию
+* Поведение по умолчанию для анимации и переходов
+* Deep linking как first class operation
+* Реализация шаблонов навигации пользовательского интерфейса (таких как navigation drawer и bottom navigation) с небольшой дополнительной работой
+* Безопасность типов при передаче информации во время навигации
+* Инструменты Android Studio для визуализации и редактирования navigation flow приложения
+ 
+## Создаем проект
+
+Cоздать простое приложение с панелью навигации можно, не написав ни одной строчки кода. Достаточно воспользоваться готовым макетом Bottom Navigation Activity на этапе создания проекта в Android Studio. При этом создается проект приложения с нижней панелью навигации BottomNavigationView на главном экране, в которой отображается три пункта. При нажатии каждого из них меняются экраны приложения. Это все хорошо, скажете вы, но если нужно добавить или убрать экраны и пункты для них? Или добавить нижнюю навигацию в существующее приложение?
+
+Ок, давайте изучим структуру проекта, созданного по шаблону, и по ходу попробуем добавить сюда пару новых экранов и пунктов для них~~, а также рассмотрим шаги для добавления нижней панели навигации в уже существующее приложение~~.
+
+## Неоходимые библиотеки
+
+Если открыть файл сборки build.gradle, в секции dependencies можно увидеть подключенные библиотеки из пакета androidx.navigation (с ktx  для проектов на kotlin)  которые нам уже знакомы по предыдущим урокам на тему навигации в приложении. Если вы добавляете нижнюю навигацию в существующее приложение, то начинать нужно с добавления этих библиотек в ваш проект.
+
+```
+implementation 'androidx.navigation:navigation-fragment-ktx:2.4.1'
+implementation 'androidx.navigation:navigation-ui-ktx:2.4.1'
+```
+
+## Граф навигации
+
+Архитектурный компонент **Navigation** позволяет упростить реализацию навигации между экранами в вашем приложении. По умолчанию, **Navigation** поддерживает фрагменты (Fragments) и активности (Activities) в качестве экранов назначения, но вы также можете добавить поддержку новых типов экранов назначения. Набор экранов назначения называется **навигационным графом (navigation graph) приложения**.
+
+Помимо экранов назначения на навигационном графе есть соединения между ними, называемые действиями (actions). Следующий рисунок демонстрирует визуальное представление навигационного графа для простого приложения из шести экранов назначения, соединённых пятью действиями.
+
+![](../img/android001.png)
+
+### Обзор компонента навигации
+Компонент навигации состоит из трех ключевых частей:
+
+* **Navigation graph**: ресурс XML, который содержит всю связанную с навигацией информацию в одном централизованном месте. Он включает в себя все отдельные области содержимого в вашем приложении, называемые destinations (пункты назначения), а также возможные пути, которые пользователь может пройти через ваше приложение.
+* **NavHost**: Пустой контейнер, который отображает пункты назначения из вашего графика навигации. Компонент Navigation содержит реализацию NavHost по умолчанию — NavHostFragment, которая отображает фрагменты — места назначения.
+* **NavController**: Объект, который управляет навигацией приложения в NavHost. NavController управляет перемещениями контента мест назначения в NavHost, в процессе перемещения пользователей по приложению.
+ 
+Мы используем объект **NavController**, сообщая ему путь в ресурсе **Navigation Graph**. Затем объекту NavController будет показан соответствующий пункт назначения в NavHostFragment.
+
+Давайте посмотрим, как это выглядит на практике, начиная с нового ресурса **Navigation Graph**.
+
+### Destinations
+
+Компонент навигации представляет концепцию **Destinations** —  пункта назначения. Пункт назначения — это любое место, в котором вы можете перемещаться в приложении, обычно это фрагмент или активити. Они поддерживаются «из коробки», но вы также можете создавать свои собственные типы назначения, если это необходимо.
+
+### Navigation Graph
+
+Navigation Graph представляет собой новый тип ресурса, который определяет все возможные пути, доступные пользователю в приложении. Он показывает визуально все пункты назначения, которые могут быть достигнуты из данного пункта назначения. Редактор навигации Android Studio отображает Navigation Graph наглядно.
+
+### Редактор навигации
+
+1. Откройте файл `res/navigation/mobile_navigation.xml` 
+1. Перейдите в режим «Дизайн»:
+
+    ![](../img/android002.png)
+
+    Navigation Graph показывает доступные пункты назначения. Стрелки между пунктами назначения называются actions (действия). О них мы поговорим позже.
+
+1. В панели атрибутов (справа) можно посмотреть и отредактировать атрибуты пунктов назначения и действий.
+
+### Анатомия навигационного XML-файла
+
+Все изменения, которые вы делаете в графическом редакторе навигации, изменяют базовый XML-файл, подобно тому, как редактор макетов изменяет XML-макет.
+
+Перейдите на вкладку «Code»:
+
+Вы увидите такой XML-код:
+
+```xml
+<navigation xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:app="http://schemas.android.com/apk/res-auto"
+            xmlns:tools="http://schemas.android.com/tools"
+    
+    app:startDestination="@+id/navigation_home">
+
+    <!-- здесь находятся теги с фрагментами и активностями -->
+
+</navigation>
+```
+
+**Примечание:**
+
+* `<navigation>` является корневым узлом каждого навигационного графа.
+* `<navigation>` содержит один или несколько пунктов назначения, представленных элементами `<activity>` и/или `<fragment>`.
+* `app:startDestination`` является атрибутом, который указывает место назначения, которое запускается по умолчанию, когда пользователь впервые открывает приложение.
+
+Давайте посмотрим на место назначения фрагмента:
+
+```xml
+<fragment
+        android:id="@+id/navigation_home"
+        android:name="ru.yotc.ui.home.HomeFragment"
+        android:label="@string/title_home"
+        tools:layout="@layout/fragment_home">
+    <argument
+        .../>
+
+    <action
+        android:id="@+id/next_action"
+        app:destination="@+id/flow_step_two_dest">
+    </action>
+</fragment>
+``` 
+
+**Примечание:**
+
+* **android:id** определяет идентификатор для фрагмента, который вы можете использовать для ссылки на место назначения в другом месте этого XML и вашего кода.
+* **android:name** объявляет полное имя класса фрагмента для создания экземпляра при переходе к этому месту назначения.
+* **tools:layout** указывает, какой макет должен отображаться в графическом редакторе.
+
+Некоторые теги `<fragment>` также могут содержать `<action>`, `<argument>` и `<deepLink>`, их мы рассмотрим позже.
+
+Пример приложения уже содержит несколько пунктов назначения на графе. Давайте мы добавим новое назначение.
+
+### Добавление нового назначения в граф навигации
+
+1. Создайте новый фрагмент с именем **SettingsFragment** (в контекстном меню **res -> layout** выбрать **new fragment**)
+1. Откройте файл `res/navigation/mobile_navigation.xml`
+1. Нажмите значок «Новый пункт назначения» и выберите «fragment_settings»
+
+    ![](../img/android003.png)
+
+Результатом является новое назначение, которое отображает предварительный просмотр макета фрагмента в окне конструктора.
+
+Обратите внимание, что вы также можете редактировать XML-файл напрямую, чтобы добавить места назначения: `mobile_navigation.xml`
+
+```xml
+<fragment
+    android:id="@+id/settingsFragment"
+    android:name="ru.yotc.baza.SettingsFragment"
+    android:label="fragment_settings"
+    tools:layout="@layout/fragment_settings" />
+```
+
+https://habr.com/ru/post/416025/

+ 258 - 0
articles/cs_layout2.md

@@ -0,0 +1,258 @@
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/cs_mysql_connection2.md">Создание подключения к БД MySQL. Получение данных с сервера.
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/cs_pagination.md">Пагинация, сортировка, фильтрация, поиск
+</a></td><tr></table>
+
+# Вывод данных согласно макету (ListView, Image).
+
+>Старый вариант с SQL-запросами находится [тут](./cs_layout.md)
+
+>Напоминаю как выглядит макет списка продукции
+>![](../img/product_list_layout.jpg)
+>
+>Критерий | Баллы
+>---------|:-----:
+>Список продукции отображается в соответствии с макетом | 0.5
+>У каждой продукции в списке отображается изображение | 0.3
+>При отсутствии изображения отображается картинка-заглушка из ресурсов | 0.3
+
+Для создания такого макета используется элемент **ListView**
+
+В разметке вместо **DataGrid** вставляем **ListView**
+
+```xml
+<ListView
+    Grid.Row="1"
+    Grid.Column="1"
+    ItemsSource="{Binding ProductList}"
+>
+    <!-- сюда потом вставить ListView.ItemTemplate -->
+</ListView>
+```
+
+Внутри него вставляем шаблон для элемента списка (*ListView.ItemTemplate*): пока у нас только прямоугольная рамка со скруглёнными углами (в этом макете вроде скрулять не надо, возможно осталось от другого шаблона)
+
+```xml
+<ListView.ItemTemplate>
+    <DataTemplate>
+        <Border 
+            BorderThickness="1" 
+            BorderBrush="Black" 
+            CornerRadius="5">
+
+            <!-- сюда потом вставить содержимое: grid из трёх колонок -->
+
+        </Border>
+    </DataTemplate>
+</ListView.ItemTemplate>                
+```
+
+Внутри макета вставляем **Grid** из трёх колонок: для картинки, основного содержимого и стоимости.
+
+```xml
+<Grid 
+    Margin="10" 
+    HorizontalAlignment="Stretch">
+
+    <Grid.ColumnDefinitions>
+        <ColumnDefinition Width="64"/>
+        <ColumnDefinition Width="*"/>
+        <ColumnDefinition Width="auto"/>
+    </Grid.ColumnDefinitions>
+
+    <!-- сюда потом вставить колонки -->
+
+</Grid>
+```
+
+**В первой** колонке выводим изображение:
+
+```xml
+<Image
+    Width="64" 
+    Height="64"
+    Source="{Binding ImagePreview,TargetNullValue={StaticResource defaultImage}}" />
+```
+
+Обратите внимание, вместо поля *Image* я вывожу вычисляемое поле *ImagePreview* - в геттере проверяю есть ли такая картинка, т.к. наличие названия в базе не означает наличие файла на диске.
+
+Вычисляемое поле можно добавить в сгенерированный класс **Product** (файл `Models/Product.cs`), но этот файл может быть перезаписан при повторном реконструировании БД, поэтому лучше создавать модифицированные классы в другом месте. Классы в C# могут быть описаны в нескольких файлах (главное чтобы они были в одном namespace), для этого используется ключевое слово **partial**:
+
+На демо экзамене есть критерии оценки за логическую и файловую структуру, поэтому куда попало классы писать не надо. Создайте каталог `Classes` и в нём класс **Common**. В созданном классе поменяйте namespace (напоминаю, оно должно быть таким же, как у модели) и добавьте класс **Product** с вычисляемыми полями:  
+
+```cs
+namespace WpfApp3.Models
+{
+    public partial class Product
+    {
+        public Uri ImagePreview
+        {
+            get
+            {
+                var imageName = Environment.CurrentDirectory + (Image ?? "");
+                return System.IO.File.Exists(imageName) ? new Uri(imageName) : null;
+            }
+        }
+
+        public string TypeAndName
+        {
+            get
+            {
+                // обратите внимание, мы читаем свойство Title виртуального поля ProductType
+                return ProductType?.Title + " | " + Title;
+            }
+        }
+    }
+}
+```
+
+1. Файлы подгружаемые с диска должны быть в формате **Uri**, иначе программа ищет их в ресурсах исполняемого файла
+1. К имени файла добавляю путь к текущему каталогу 
+1. Если такого файла нет, то возвращаю **null**, в этом случае срабатывает параметр привязки *TargetNullValue* - отображать изображение по-умолчанию.
+1. Изображение по-умолчанию задается в ресурсах окна (первый элемент в теге Window)
+
+    ```xml
+    <Window.Resources>
+        <BitmapImage 
+            x:Key='defaultImage' 
+            UriSource='./Images/picture.png' />
+    </Window.Resources>
+    ```
+
+    тут, как раз, указывается путь к изображению в ресурсах (в моём случае в приложении создан каталог `Images` и в него ЗАГРУЖЕН файл)
+
+**Во второй** колонке вывожу основную информацию о продукте: тип + название, аритикул и список материалов.
+
+Так как данные выводятся в несколько строк, то заворачиваю их в **StackPanel** (тут можно использовать и **Grid**, но их и так уже много в разметке)
+
+```xml
+<StackPanel
+    Grid.Column="1"
+    Margin="5"
+    Orientation="Vertical">
+
+    <TextBlock 
+        Text="{Binding TypeAndName}"/>
+
+    <TextBlock 
+        Text="{Binding ArticleNumber}"/>
+
+    <TextBlock 
+        Text="{Binding MaterialString}"/>
+</StackPanel>
+```
+
+Вообще выводимый текст можно форматировать, но чтобы не запоминать лишних сущностей можно нарисовать ещё один геттер *TypeAndName* (в том же классе **Product**)
+
+Артикул выводится как есть
+
+Если мы сейчас попробуем запустить наше приложение, то получим исключение (что-то с **NULL**). Дело в том, что по-умолчанию в модель загружаются данные только текущей таблицы (Product), а виртуальное свойство **ProductType** остаётся не заполненным. Для того, чтобы считать связанные данные, нужно при чтении данных использовать метод **Include**:
+
+```cs
+using (var context = new esmirnovContext())
+{
+    ProductList = context.Products
+        .Include(product => product.ProductType)
+        .ToList();
+}
+```
+
+На данный момент приложение выглядит примерно так
+
+![](../img/cs005.png)
+
+Видно, что размер элемента зависит от содержимого.
+
+Чтобы это исправить нужно добавить в **ListView** стиль для элемента контейнера, в котором задать горизонтальное выравнивание по ширине:
+
+```xml
+<ListView
+    Grid.Row="1"
+    Grid.Column="1"
+    ItemsSource="{Binding ProductList}"
+>
+    <ListView.ItemContainerStyle>
+        <Style 
+            TargetType="ListViewItem">
+            <Setter 
+                Property="HorizontalContentAlignment"
+                Value="Stretch" />
+        </Style>
+    </ListView.ItemContainerStyle>
+    ...
+```
+
+Теперь окно должно выглядеть как положено:
+
+![](../img/cs006.png)
+
+# Вывод данных "плиткой"
+
+Такое задание было на одном из прошлых соревнований WorldSkills, вполне вероятно что появится и на демо-экзамене.
+
+Компоненты **ListBox** и **ListView** по умолчанию инкапсулируют все элементы списка в специальную панель **VirtualizingStackPanel**, которая располагает все элементы по вертикали. Но с помощью свойства **ItemsPanel** можно переопределить панель элементов внутри списка. 
+
+Мы будем использовать уже знакомую вам **WrapPanel**:
+
+```xml
+<ListView.ItemsPanel>
+    <ItemsPanelTemplate>
+        <WrapPanel 
+            HorizontalAlignment="Center" />
+    </ItemsPanelTemplate>
+</ListView.ItemsPanel>
+```
+
+>Атрибут *HorizontalAlignment* используем, чтобы "плитки" центрировались.
+
+![](../img/01072.png)
+
+Как видим, элементы отображаются горизонтальным списком, но нет переноса. Для включения переноса элементов нужно в **ListView** отключить горизонтальный скролл, добавив атрибут `ScrollViewer.HorizontalScrollBarVisibility="Disabled"`:
+
+![](../img/01073.png)
+
+Свойство *ItemContainerStyle* уже не нужно и его можно убрать.
+
+Размеры наших элементов по-прежнему зависят от содержимого - тут надо править шаблон **ItemTemplate**.
+
+Итоговая разметка для вывода "плиткой" должна выглядеть примерно так:
+
+```xml
+<ListView
+    ItemsSource="{Binding ProductList}"
+    x:Name="ListView"
+    ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
+>
+    <ListView.ItemsPanel>
+        <ItemsPanelTemplate>
+            <WrapPanel 
+                HorizontalAlignment="Center" />
+        </ItemsPanelTemplate>
+    </ListView.ItemsPanel>
+
+    <!--ListView.ItemContainerStyle>
+        <Style 
+            TargetType="ListViewItem">
+            <Setter 
+                Property="HorizontalContentAlignment"
+                Value="Stretch" />
+        </Style>
+    </-->
+    
+    
+    <ListView.ItemTemplate>
+        ...
+    </ListView.ItemTemplate>
+</ListView>    
+```
+
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/cs_mysql_connection2.md">Создание подключения к БД MySQL. Получение данных с сервера.
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/cs_pagination.md">Пагинация, сортировка, фильтрация, поиск
+</a></td><tr></table>

+ 4 - 2
articles/cs_mysql_connection3.md

@@ -134,12 +134,14 @@ Scaffold-DbContext "server=kolei.ru;database=esmirnov;uid=esmirnov;password=1111
     public partial class MainWindow : Window
     {
         public IEnumerable<Product> ProductList { get; set; }
-        public esmirnovContext dbContext = new esmirnovContext();
         public MainWindow()
         {
             InitializeComponent();
             DataContext = this;
-            ProductList = dbContext.Products.ToList();
+            using (var context = new esmirnovContext())
+            {
+                ProductList = context.Products.ToList();
+            }
         }
     }
     ```

binární
img/android001.png


binární
img/android002.png


binární
img/android003.png


binární
img/cs005.png


binární
img/cs006.png


+ 3 - 1
readme.md

@@ -224,7 +224,7 @@ https://office-menu.ru/uroki-sql Уроки SQL
 
 1. [Создание подключения к БД MySQL. Получение данных с сервера.](./articles/cs_mysql_connection3.md)
 
-1. [Вывод данных согласно макету (ListView, Image). Вывод данных плиткой.](./articles/cs_layout.md)<!-- сюда же добавить разметку "плиткой" -->
+1. [Вывод данных согласно макету (ListView, Image). Вывод данных плиткой.](./articles/cs_layout2.md)<!-- сюда же добавить разметку "плиткой" -->
 
 1. [Пагинация, сортировка, фильтрация, поиск](./articles/cs_pagination.md)<!-- datepicker -->
 
@@ -289,6 +289,8 @@ https://office-menu.ru/uroki-sql Уроки SQL
 
 1. [Проект "база". Авторизация на сервере (Basic auth, token).](./articles/android_auth.md)
 
+1. [Android Navigation. Знакомство с BottomNavigationView.](./articles/android_bottom_navigation.md)
+
 1. [Wear OS](./articles/wear_os.md)
 
 1. [Разбор задания предыдущего чемпионата](./articles/wsrf6_1.md)