ソースを参照

стили, фигуры, события

Евгений Колесников 4 年 前
コミット
0ef00dc639
14 ファイル変更465 行追加12 行削除
  1. 16 0
      articles/android_studio.md
  2. 22 12
      articles/kotlin.md
  3. 425 0
      articles/themes.md
  4. BIN
      img/04020.webp
  5. BIN
      img/04022.png
  6. BIN
      img/04023.png
  7. BIN
      img/04024.png
  8. BIN
      img/04025.png
  9. BIN
      img/04026.png
  10. BIN
      img/04027.png
  11. BIN
      img/04028.png
  12. BIN
      img/04029.png
  13. BIN
      img/04030.png
  14. 2 0
      readme.md

+ 16 - 0
articles/android_studio.md

@@ -1,3 +1,11 @@
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/kotlin.md">основы Kotlin
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/themes.md">Темы. Фигуры. Обработчики событий.
+</a></td><tr></table>
+
 # Первый проект в Android Studio
 
 >Основано на [этих лекциях](https://metanit.com/java/android/1.2.php) (адаптировано под Kotlin)
@@ -403,3 +411,11 @@ val textView: TextView = findViewById(R.id.header)
 Практическое задание:
 
 Используя **LinearLayout** нарисовать разметку для калькулятора (за основу взять калькулятор Windows)
+
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/kotlin.md">основы Kotlin
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/themes.md">Темы. Фигуры. Обработчики событий.
+</a></td><tr></table>

+ 22 - 12
articles/kotlin.md

@@ -3,7 +3,7 @@
 </a></td><td style="width: 20%;">
 <a href="../readme.md">Содержание
 </a></td><td style="width: 40%;">
-<a href="../articles/kotlin.md">Основы языка Kotlin
+<a href="../articles/android_studio.md">Первый проект в Android Studio
 </a></td><tr></table>
 
 # Основы языка Kotlin
@@ -1952,7 +1952,7 @@ fun operation(x: Int, y: Int, op: (Int, Int) ->Int){
 
 Передача анонимной функции в данном случае аналогична передачи лямбда-выражения, в то же время лямбда-выражения представляют более лаконичный синтаксис, поэтому, как правило, в этой роли применяются лямбда-выражения.
 
-# Исключения
+## Исключения
 
 В любой, особенно большой, программе могут возникать ошибки, приводящие к ее неработоспособности или к тому, что программа делает не то, что должна. Причин возникновения ошибок много.
 
@@ -1962,7 +1962,7 @@ fun operation(x: Int, y: Int, op: (Int, Int) ->Int){
 
 На этот случай в языках программирования, в том числе Котлин, существует специальный оператор, позволяющий перехватывать возникающие исключения и обрабатывать их так, чтобы программа продолжала работать или корректно завершала свою работу.
 
-## Классы исключений
+### Классы исключений
 
 Все исключения в Kotlin являются наследниками класса **Throwable**. У каждого исключения есть сообщение, трассировка стека, а также причина, по которой это исключение вероятно было вызвано.
 
@@ -1972,7 +1972,7 @@ fun operation(x: Int, y: Int, op: (Int, Int) ->Int){
 throw MyException("Hi There!")
 ```
 
-## try
+### try
 
 Оператор try позволяет перехватывать исключения
 
@@ -1993,7 +1993,7 @@ finally {
 
 В коде может быть любое количество блоков catch (такие блоки могут и вовсе отсутствовать). Блоки finally могут быть опущены. Однако, должен быть использован как минимум один блок catch или finally.
 
-## Try - это выражение
+### Try - это выражение
 
 Ключевое слово try является выражением, то есть оно может иметь возвращаемое значение.
 
@@ -2003,7 +2003,7 @@ val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
 
 Возвращаемым значением будет либо последнее выражение в блоке try, либо последнее выражение в блоке catch (или блоках). Содержимое finally блока никак не повлияет на результат try-выражения.
 
-# Регулярные выражения в Котлине
+## Регулярные выражения в Котлине
 
 Для описания регулярных выражений в Котлине используется тип **Regex**. Для создания регулярного выражения следует вызвать его конструктор, например Regex("KotlinAsFirst"). Второй способ создания регулярного выражения — вызов функции toRegex() на строке-получателе, например "KotlinAsFirst".toRegex().
 
@@ -2080,7 +2080,7 @@ fun timeStrToSeconds(str: String): Int {
 
 Здесь мы разбираем исходную строку вида «12:34:56» с целью найти в ней три одинаковых группы поиска (\d\d). Каждая из групп поиска включает в себя две цифры. Убедившись с помощью проверки на null, что регулярное выражение успешно найдено, мы отбрасываем первый элемент groupValues с помощью функции drop(1), оставляя, таким образом, в списке только значения трёх групп поиска. Далее каждая из пар цифр конвертируется в число. Результат сворачивается в число секунд, прошедших с начала дня, с помощью функции высшего порядка fold
 
-# Асинхронное выполнение кода (Корутины)
+## Асинхронное выполнение кода (Корутины)
 
 Создание слишком большого количества потоков может на самом деле сделать приложение неэффективным в некоторых ситуациях; потоки - это объекты, которые накладывают накладные расходы во время размещения объектов и сборки мусора.
 
@@ -2109,7 +2109,7 @@ dependencies {
 [дальше перевод официальной доки](https://kotlinlang.org/docs/reference/coroutines/coroutines-guide.html)
 
 
-## Основы
+### Основы
 
 Здесь рассмотрим основные концепции.
 
@@ -2323,7 +2323,7 @@ I'm sleeping 2 ...
 
 Корутины запущенные в *GlobalScope* не препятсствуют завершению процесса. Этим они и похожи на "демонов".
 
-## Прерывание работы и таймауты
+### Прерывание работы и таймауты
 
 ### Прерывание работы корутины
 
@@ -2497,7 +2497,7 @@ I'm sleeping 2 ...
 Result is null
 ```
 
-## Составление Suspending функций
+### Составление Suspending функций
 
 В этом разделе рассматриваются различные подходы к составлению *Suspending* функций.
 
@@ -2683,7 +2683,7 @@ First child was cancelled
 Computation failed with ArithmeticException
 ```
 
-## Корутины: контекст и диспетчеры
+### Корутины: контекст и диспетчеры
 
 Корутины всегда выполняются в некотором контексте, представленном значением типа *CoroutineContext*, определенного в стандартной библиотеке Kotlin.
 
@@ -3046,7 +3046,7 @@ ThreadLocal может использоваться с любыми примит
 
 Для расширенного использования, например для интеграции с журналированием MDC, транзакционными контекстами или любыми другими библиотеками, которые внутренне используют локальные потоки для передачи данных, см. документацию интерфейса ThreadContextElement, который должен быть реализован.
 
-## Асинхронные потоки
+### Асинхронные потоки
 
 Suspend функции асинхронно возвращают одно значение, но как мы можем вернуть несколько асинхронно вычисленных значений? Вот тут-то и появляются потоки Котлина.
 
@@ -4032,3 +4032,13 @@ data class Person(val name: String) {
     person2.age = 20
     println("${person1 == person2}") // выведет "true"
 ```
+
+# Контрольные вопросы
+
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/cs_edit_product.md">Создание, изменение продукции
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/android_studio.md">Первый проект в Android Studio
+</a></td><tr></table>

+ 425 - 0
articles/themes.md

@@ -0,0 +1,425 @@
+<table style="width: 100%;"><tr><td style="width: 40%;">
+<a href="../articles/android_studio.md">Первый проект в Android Studio
+</a></td><td style="width: 20%;">
+<a href="../readme.md">Содержание
+</a></td><td style="width: 40%;">
+<a href="../articles/themes.md">Темы. Фигуры. Обработчики событий.
+</a></td><tr></table>
+
+`&lt;` `&gt;`
+
+# Стили и темы
+
+## Стили
+
+Мы можем настроить элемент с помощью различных атрибутов, которые задают высоту, ширину, цвет фона, текста и так далее. Но если у нас несколько элементов используют одни и те же настройки, то мы можем объединить эти настройки в стили.
+
+Например, пусть у нас есть несколько элементов TextView:
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+ 
+    <TextView
+        android:id="@+id/textView1"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:textSize="28sp"
+        android:textColor="#3f51b5"
+        android:text="Android Lollipop"
+ 
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintBottom_toTopOf="@+id/textView2"
+        />
+    <TextView
+        android:id="@+id/textView2"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:textSize="28sp"
+        android:textColor="#3f51b5"
+        android:text="Android Marshmallow"
+ 
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintBottom_toTopOf="@+id/textView3"
+        app:layout_constraintTop_toBottomOf="@+id/textView1"
+        />
+    <TextView
+        android:id="@+id/textView3"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:textSize="28sp"
+        android:textColor="#3f51b5"
+        android:text="Android Nougat"
+ 
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/textView2"
+        />
+ 
+</androidx.constraintlayout.widget.ConstraintLayout>
+```
+
+![](../img/04022.png)
+
+Все эти **TextView** имеют одинаковый набор свойств, и, к примеру, если нам захочется изменить цвет текста, то придется менять его у всех трех TextView. Данный подход не оптимален, а более оптимальный подход представляет использование стилей, которые определяются в проекте в папке `res/values`.
+
+Итак, добавим в проект в папку `res/values` новый элемент Value Resourse File, который назовем `styles.xml`:
+
+
+![](../img/04023.png)
+
+Определим в файле styles.xml следующее содержимое:
+
+```xml
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="TextViewStyle">
+        <item name="android:layout_width">0dp</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:textColor">#3f51b5</item>
+        <item name="android:textSize">28sp</item>
+        <item name="android:gravity">center</item>
+    </style>
+</resources>
+```
+
+Здесь определен новый стиль **TextViewStyle**, который с помощью элементов **item** задает значения для атрибутов **TextView**.
+
+Стиль задается с помощью элемента `<style>`. Атрибут **name** указывает на название стиля, через которое потом можно ссылаться на него. Необязательный атрибут **parent** устанавливает для данного стиля родительский стиль, от которого дочерний стиль будет наследовать все свои характеристики.
+
+С помощью элементов **item** устанавливаются конкретные свойства виджета, который принимает в качестве значения атрибута **name** имя устанавливаемого свойства.
+
+Теперь применим стиль элементов TextView в файле `activity_main.xml`:
+
+```xml
+<TextView
+    android:id="@+id/textView1"
+        
+    style="@style/TextViewStyle"
+    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+    android:text="Android Lollipop"
+    app:layout_constraintLeft_toLeftOf="parent"
+    app:layout_constraintRight_toRightOf="parent"
+    app:layout_constraintTop_toTopOf="parent"
+    app:layout_constraintBottom_toTopOf="@+id/textView2"
+    />
+```
+
+Используя определение `style="@style/TextViewStyle"` текстовое поле связывается с определением стиля. Итоговый результат буде тот же, что и раньше, только кода становится меньше. А если мы захотим поменять какие-то характеристики, то достаточно изменить нужный элемент item в определении стиля.
+
+## Темы
+
+Кроме применение отдельных стилей к отдельным элементам, мы можем задавать стили для всего приложения или activity в виде тем. Тема предтавляет коллекцию атрибутов, которые применяются в целом ко всему приложению, классу activity или иерархии виджетов.
+
+Мы можем сами создать тему. Однако Android уже предоставляет несколько предустановленных тем для стилизации приложения, например, Theme.AppCompat.Light.DarkActionBar и ряд других.
+
+По умолчанию приложение уже применяет темы. Так, откроем файл AndroidManifest.xml. В нем мы можем увидеть следующее определение элемента application, представляющего приложение:
+
+```xml
+<application
+    android:allowBackup="true"
+    android:icon="@mipmap/ic_launcher"
+    android:label="@string/app_name"
+    android:roundIcon="@mipmap/ic_launcher_round"
+    android:supportsRtl="true"
+    android:theme="@style/Theme.ViewApp">
+```        
+
+Задание темы происходит с помощью атрибута *android:theme*. В данном случае используется ресурс, который называется в моем случае *Theme.ViewApp*. По умолчанию файлы тем определены в папке `res/values`. В частности, здесь можно найти условный каталог themes, в котором по умолчанию есть два элемента: `themes.xml`:
+
+Дальше про темы читайте в [оригинальной лекции](https://metanit.com/java/android/6.2.php)
+
+# Работа с ресурсами
+
+Ресурс в приложении Android представляет собой файл, например, файл разметки интерфейса или некоторое значение, например, простую строку. То есть ресурсы представляют собой и файлы разметки, и отдельные строки, и звуковые файлы, файлы изображений и т.д. Все ресурсы находятся в проекте в каталоге `res`. Для различных типов ресурсов, определенных в проекте, в каталоге res создаются подкаталоги. Поддерживаемые подкаталоги:
+
+* **animator**: xml-файлы, определяющие анимацию свойств
+* **anim**: xml-файлы, определяющие tween-анимацию
+* **color**: xml-файлы, определяющие список цветов
+* **drawable**: Графические файлы (.png, .jpg, .gif) или описание фигур в .xml формате
+* **mipmap**: Графические файлы, используемые для иконок приложения под различные разрешения экранов
+* **layout**: xml-файлы, определяющие пользовательский интерфейс приложения
+* **menu**: xml-файлы, определяющие меню приложения
+* **raw**: различные файлы, которые сохраняются в исходном виде
+* **values**: xml-файлы, которые содержат различные используемые в приложении значения, например, ресурсы строк
+* **xml**: Произвольные xml-файлы
+* **font**: файлы с определениями шрифтом и расширениями .ttf, .otf или .ttc, либо файлы XML, который содержат элемент `<font-family>`
+
+## Применение ресурсов
+
+Существует два способа доступа к ресурсам: в файле исходного кода и в файле xml.
+
+### Ссылка на ресурсы в коде
+
+Тип ресурса в данной записи ссылается на одно из пространств (вложенных классов), определенных в файле `R.java`, которые имеют соответствующие им типы в xml:
+
+* **R.drawable** (ему соответствует тип в xml drawable)
+* **R.id** (id)
+* **R.layout** (layout)
+* **R.string** (string)
+* **R.attr** (attr)
+* **R.plural** (plurals)
+* **R.array** (string-array)
+
+Например, для установки ресурса `activity_main.xml` в качестве графического интерфейса в коде **MainActivity** в методе *onCreate()* есть такая строка:
+
+```kt
+setContentView(R.layout.activity_main)
+```
+
+Через выражение **R.layout.activity_main** мы и ссылаемся на ресурс `activity_main.xml`, где **layout** - тип ресурса, а **activity_main** - имя ресурса.
+
+Подобным образом мы можем получать другие ресурсы. Например, в файле `res/values/strings.xml` определен ресурс **app_name**:
+
+```xml
+<resources>
+    <string name="app_name">ViewApp</string>
+</resources>
+```
+
+Этот ресурс ссылается на строку. Чтобы получить ссылку на данный ресурс в коде, мы можем использовать выражение **R.string.app_name**.
+
+### Доступ в файле xml
+
+Нередко возникает необходимость ссылаться на ресурс в файле xml, например, в файле, который определяет визуальный интерфейс, к примеру, в `activity_main.xml`. Ссылки на ресурсы в файлах xml имеют следующую формализованную форму: `@[имя_пакета:]тип_ресурса/имя_ресурса`
+
+* имя_пакета представляет имя пакета, в котором ресурс находится (указывать необязательно, если ресурс находится в том же пакете)
+
+* тип_ресурса представляет подкласс, определенный в классе R для типа ресурса
+
+* имя_ресурса имя файла ресурса без расширения или значение атрибута android:name в XML-элементе (для простых значений).
+
+Например, мы хотим вывести в элемент **TextView** строку, которая определена в виде ресурса в файле `strings.xml`:
+
+```xml
+<TextView
+    android:id="@+id/textView"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:text="@string/app_name" />
+```
+
+В данном случае свойство *text* в качестве значения будет получать значение строкового ресурса **app_name**.
+
+Подробнее про ресурсы можно посмотреть в [отдельной лекции](https://metanit.com/java/android/2.5.php)
+
+# Фигуры в качестве фона для визуальных элементов
+
+Часто применяется для круглых кнопок.
+
+Вообще для задания цвета фона используется атрибут *android:background*, но в андроиде невозможно задать скругленные углы, чтобы получить круглые кнопки (или прямоугольные со скруглёнными углами).
+
+Для реализации такой фичи можно в качестве фона задать *drawable* ресурс: `@drawable/название_файла_ресурса`
+
+А в качестве *drawable* ресурса может быть, как мы уже знаем, картинка или фигура.
+
+Про фигуры расскажу подробнее:
+
+В каталоге `res/drawable` создайте "Drawable Resource File" c корневым элементом **shape** ([фигура](https://developer.android.com/guide/topics/resources/drawable-resource#Shape))
+
+В качестве фигуры можно использовать 4 примитива. 
+
+* **rectangle** - прямоугольник
+* **oval** - овал
+* **line** - горизонтальная линия
+* **ring** - кольцо
+
+Для фигуры **ring** в теге **shape** могут быть определены дополнительные атрибуты:
+
+* **android:innerRadius** - внутренний радиус
+* 
+
+Также в теге **shape** могут быт вложенные теги для настройки фигур:
+
+* **stroke** - можно задать толщину и цвет границы фигуры, например овал с тонкой черной границей:
+
+    ```xml
+    <?xml version="1.0" encoding="utf-8"?>
+    <shape
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:shape="oval">
+        <stroke
+            android:width="1dp"
+            android:color="#000000">
+        </stroke>
+    </shape>
+    ```
+
+    ![](../img/04024.png)
+
+    ещё у тега **stroke** есть атрибуты *android:dashWidth* - ширина пунктирной линии и *android:dashGap* - промежуток между линиями
+
+* **solid** - задаёт цвет заливки фигуры
+    
+    ```xml
+    <?xml version="1.0" encoding="utf-8"?>
+    <shape
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:shape="rectangle">
+        <stroke
+            android:width="2dp"
+            android:color="#0000FF">
+        </stroke>
+        <solid
+            android:color="#00FF00">
+        </solid>
+    </shape>
+    ```
+
+    ![](../img/04025.png)
+
+* **size** - по умолчанию фигура занимает все доступное ей пространство, но мы можем явно указать её размер с помощью тега size.
+
+    ```xml
+    <?xml version="1.0" encoding="utf-8"?>
+    <shape
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:shape="rectangle">
+        <stroke
+            android:width="2dp"
+            android:color="#0000FF">
+        </stroke>
+        <size
+            android:width="100dp"
+            android:height="100dp">
+        </size>
+    </shape>
+    ```
+
+    ![](../img/04026.png)
+
+* **padding** - позволяет нам задать величину отступа внутри фигуры. Это актуально, например, для **TextView**. Отступ будет учтен при размещении текста.
+
+    ```xml
+    <?xml version="1.0" encoding="utf-8"?>
+    <shape
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:shape="rectangle">
+        <stroke
+            android:width="2dp"
+            android:color="#0000FF">
+        </stroke>
+        <padding
+            android:bottom="5dp"
+            android:top="20dp"
+            android:left="50dp"
+            android:right="30dp">
+        </padding>
+    </shape>
+    ```
+
+    ![](../img/04027.png)
+
+* **gradient** - заливка с переходом из одного цвета в другой (можно с переходом через третий цвет). Градиенту можно указать угол и тип (линейный, радиальный).
+
+* **corners** - с помощью этого тега мы можем задать скруглённые углы для прямоугольника
+
+    ```xml
+    <?xml version="1.0" encoding="utf-8"?>
+    <shape
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:shape="rectangle">
+        <stroke
+            android:width="1dp"
+            android:color="#000000">
+        </stroke>
+        <corners
+            android:radius="40dp">
+        </corners>
+    </shape>
+    ```
+
+    ![](../img/04028.png)
+
+    Причем можно задать свой радиус для каждого угла
+
+    ```xml
+    <?xml version="1.0" encoding="utf-8"?>
+    <shape
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:shape="rectangle">
+        <stroke
+            android:width="1dp"
+            android:color="#000000">
+        </stroke>
+        <corners
+            android:topLeftRadius="15dp"
+            android:topRightRadius="30dp"
+            android:bottomRightRadius="45dp"
+            android:bottomLeftRadius="60dp">
+        </corners>
+    </shape>
+    ```
+
+    ![](../img/04029.png)
+
+* **ring** - кольцо. Для кольца мы можем настроить два параметра: размер внутреннего радиуса и толщина кольца. Причем, эти два параметра мы можем указывать в абсолютном и относительном выражении.
+
+    ```xml
+    <?xml version="1.0" encoding="utf-8"?>
+    <shape
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:innerRadius="50dp"
+        android:shape="ring"
+        android:thickness="40dp"
+        android:useLevel="false">
+        <solid
+            android:color="#FF0000">
+        </solid>
+        <size
+            android:height="200dp"
+            android:width="200dp">
+        </size>
+    </shape>
+    ```
+
+    ![](../img/04030.png)
+
+    Подробнее про режимы отображения кольца (и других фигур) можно посмотреть [здесь](https://startandroid.ru/ru/uroki/vse-uroki-spiskom/377-urok-162-grafika-drawable-shape-gradient.html)
+
+# Обработка событий (клик по кнопке)
+
+Для кнопок (тег **Button**) можно задать обработчик клика, для этого в разметке добавьте атрибут *android:onClick* и задайте имя функции обработчика:
+
+```xml
+<Button
+    ...
+    android:onClick="onOperationClick"/>
+```
+
+Чтобы не писать вручную эту функцию можно нажать на её названии клавиши `Alt+Enter` и выбрать действие "Create onOperationClick(view) in MainActivity"
+
+Один и тот же обработчик можно назначить нескольким кнопкам (в калькуляторе можно сделать всего два обработчика: один для числовых кнопок, а второй для функциональных)
+
+В коде обработчика мы должны либо привести параметр *view* к типу **Button**: 
+
+```kt
+fun onOperatorClick(view: View) {
+    when((view as Button).text){
+        "*" -> {
+            // реализовать операцию умножения 
+        }
+    }
+}
+```
+
+Либо в разметке добавить кнопкам атрибут *android:tag* и в коде анализировать его содержимое (ему приведение типов не нужно, т.к. он объявлен в классе **View**)
+
+# Задание
+
+* используя стили раскрасить функциональные и цифровые кнопки калькулятора
+* сделать кнопки со скругленными углами используя фигуры
+* создайте обработчики нажатия кнопок и реализуйте работу калькулятора
+

BIN
img/04020.webp


BIN
img/04022.png


BIN
img/04023.png


BIN
img/04024.png


BIN
img/04025.png


BIN
img/04026.png


BIN
img/04027.png


BIN
img/04028.png


BIN
img/04029.png


BIN
img/04030.png


+ 2 - 0
readme.md

@@ -134,6 +134,8 @@ http://sergeyteplyakov.blogspot.com/2014/01/microsoft-fakes-state-verification.h
 
 2. [Первый проект в Android Studio](./articles/android_studio.md)
 
+3. [Стили и темы. Ресурсы. Фигуры. Обработчики событий.](./articles/themes.md)
+
 <!--
 
 https://office-menu.ru/uroki-sql Уроки SQL