4. Создание простейшего приложения

В данном разделе мы создадим очень простое приложение на Android, познакомимся с понятием Activity, со структурой окна, научимся работать с элементами UI и обрабатывать простые события.

Изучение Android начнем с создания простейшего приложения. Вернемся в главное окно, в панели Project выберем представление Android и подробнее разберемся в тех файлах, которые есть в нашем проекте.

Прежде всего, нас интересуют два файла - (1) MainActivity.java и (2) activity_main.xml, которые описывают и программируют стартовое Activity нашего приложения, которое было добавлено мастером создания нового проекта.

Базовым "строительным блоком" пользовательского интерфейса является Acitivty (активити, активность, операция). В первом приближении вы можете воспринимать Activity как аналог окна в десктопном приложении или страницы в классическом веб-приложении. Activity представляет пользовательский интерфейс и, во многих случаях, точку входа в ваше приложение.

1. Определение Activity

Activity — это компонент приложения, который представляет собой экран с пользовательским интерфейсом и функционалом, и с которым пользователи могут взаимодействовать для выполнения каких-либо действий, например набрать номер телефона, сделать фото, отправить письмо или просмотреть карту. Каждой Activity соответствует окно для прорисовки соответствующего пользовательского интерфейса. Обычно окно отображается во весь экран, однако его размер может быть меньше, и оно может размещаться поверх других окон (в последнее время стало распространена технология split-screen, когда приложение занимает только часть экрана, а также ваше приложение может быть запущено в режиме multi-window на устройстве ChromeOS и подобных ему).

Количество Activity в приложении может быть 1 или больше. Activity, которая запускается первой, считается стартовой. Из нее можно запустить другую Activity этого же приложения, а также Activity других приложений.

Подробное описание Activity можете прочесть здесь.

2. Контроллер Activity

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

В нашем проекте, по умолчанию, уже есть один стартовый экран, который вы могли видеть при запуске пустого Android-проекта.

Класс контроллера нашего стартового окна находится в директории src\main\java\<название пакета>\MainActivity.java.

Рассмотрим его подробнее.

MainActivity.java
package ua.opu.myfirstandroidapp;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Обратите внимание, что класс контроллера наследуется от класса AppCompatActivity. Этот класс входит в набор библиотек Android Support Library, который предназначен для обеспечения обратной совместимости Activity на старый версиях операционной системы.

Схема работы механизма наследования стандартна для объектно-ориентированных языков: в классах-предках определены методы, которые отвечают за стандартное поведение класса Activity. Если мы хотим изменить поведение контроллера, мы переопределяем нужные методы.

В данном случае мы переопределяем метод onCreate(). Этот метод является callback-методом и вызывается в момент, когда операционная система создает объект класса Activity. Первым действием мы вызываем метод onCreate() суперкласса (при создании объекта Activity должны быть выполнены определенные служебные действия, которые определены в методах onCreate() в цепочке наследования), после чего мы можем программировать необходимое поведение класса Activity при создании объекта.

Обратите внимание на строку 12. В данной строке мы вызываем метод setContentView() и передаем ему ссылку на ресурс макета окна. Поговорим о нем подробнее.

3. Создание макета для Activity

Для построения пользовательского интерфейса в Android используются файлы макета в формате xml. Файлы макетов относятся к ресурсам и находятся в папке res\layout.

Построение пользовательского интерфейса в Android основано на контролах (или виджетах) и контейнерах. Контрол представляет собой элемент UI, который отображает изменяемую информацию, например, окно или текстовое поле. Контролы являются базовыми визуальными строительными блоками, сочетание и параметры которых определяет внешний вид приложения и порядок взаимодействия с приложением.

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

Вы можете использовать стандартные виджеты, подключать виджеты сторонних разработчиков, а также создавать свои собственные.

Контейнеры позволяют организовывать и располагать виджеты определенным образом. В контейнеры можно помещать виджеты, а также другие контейнеры. Каждый тип контейнера имеет свою логику организации элементов, а также свои параметры настройки.

С учетом многообразия форм, размеров и параметров android-устройств, абсолютное позиционирование элементов UI является категорически неприемлемым. При создании макета, всегда, где это возможно, старайтесь избегать указания конкретных значений, особенно, это касается размеров элементов.

Откроем файл activity_main.xml и посмотрим его содержимое.

Редактор макетов

Для работы с макетами в Android Studio встроен многофункциональный редактор макетов (Layout Editor), который позволит вам удобно конструировать самые разнообразные макеты пользовательского интерфейса.

Рассмотрим основные рабочие панели редактора макетов:

  1. область с «виджетами» – элементами пользовательского интерфейса;

  2. дерево компонентов – позволяет легко выбирать элементы и наглядно увидеть иерархию объектов в разметке;

  3. визуальное представление – так элементы будут выглядеть на экране устройства;

  4. чертеж (Blueprint view) – специальный вид, который позволяет легко увидеть взаимоотношения и наложения элементов, позволяет выделять невидимые элементы и так далее;

  5. панель с инструментами - позволяет изменить размер и тип устройства, язык, версию API, а также содержит другие инструменты;

  6. панель со свойствами - позволяет изменять свойства выделенного виджета. Изначально представлены основные свойства выделенного элемента, по ссылке внизу можно перейти к отображению всех свойств элемента;

  7. выбор представления макета - в виде xml-кода, визуально или в совмещенном виде.

Более подробно возможности и работу редактора макетов читайте здесь.

Построение пользовательского интерфейса. Менеджер ConstraintLayout

Принцип построения пользовательского интерфейса мало чем отличается от стандартов построения GUI, которые вы могли наблюдать при использовании различных инструментов построения пользовательского интерфейса - у нас есть обычные виджеты, виджеты - контейнеры, а также менеджеры разметок (Layouts), которые размещают элементы согласно той или иной логике.

На данный момент стандартный менеджером разметки является менеджер ConstraintLayout, который организует элементы с помощью системы привязок к другим элементам, направляющим, барьерам а также к сторонам экрана.

Отличную подробную статью на русском языке про работу менеджера ConstraintLayout вы можете прочитать здесь.

Исчерпывающую англоязычную документацию от разработчиков Android можно найти здесь и здесь.

Дополнительные неплохие статьи (информация может пересекаться с предыдущими источниками) на русском языке читайте здесь и здесь.

Если у вас пока что не получается совладать с менеджером разметки, перейдите в текстовый вид макета (вкладка Text внизу слева в редакторе).

Замените текст макета на следующий

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        android:textAppearance="@style/TextAppearance.AppCompat.Display2"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:text="Нажми меня"
        app:layout_constraintBottom_toTopOf="@+id/text"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</android.support.constraint.ConstraintLayout>

После этого, у вас в макете появится кнопка выше текстовой надписи. Для нашего примера этого пока что хватит

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

4. Построение UI из пользовательского макета

Отлично, у нас есть макет для окна тестового приложения, но как макет будет преобразован в дерево элементов графического интерфейса?:

Как мы знаем, в объектно-ориентированных языках всё является объектами, в том числе элементы графического интерфейса. Однако, создание объектов в исходном коде на языке Java является неэффективным, а также противоречит принципу отделения представления от бизнес-логики приложения.

Для построения UI в Android используется следующий подход:

  1. пользовательский интерфейс описывается с помощью xml-файла макета;

  2. специальный объект считывает содержимое xml-файла (этот процесс называется «парсингом») и на его основе создает иерархию объектов графического интерфейса. Таким образом, xml-описание интерфейса превращается в дерево объектов различных классов, которые связаны иерархией. В Android это реализует объект класса android.view.LayoutInflater. Процесс преобразования xml-макета в дерево объектов называется inflation;

  3. созданное с помощью LayoutInflater дерево объектов «присоединяется» к существующей иерархии объектов окна.

Вернемся к класс контроллера окна.

MainActivity.java
package ua.opu.pnit.helloworld;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

Еще раз обратим внимание на метод setContentView(). Он принимает ссылку на xml-файл макета (что означает R.layout мы разберем ниже). Этот метод работает следующим образом: он запрашивает LayoutInflater, который ему предоставляет ОС, после чего полученное дерево объектов «пристегивается» к окну. Фактически, он выполняет пункты 2 и 3, которые были описаны выше.

Если говорить проще, метод setContentView() как бы «подключает» разметку к нашему окну.

5. Работа с элементами UI в классе Activity

Согласно принципу разделения обязанностей, а также семейству архитектурных паттернов MV*, работа с объектами UI, а также обработка событий должна происходить в классе контроллера, то есть, в нашем случае в классе MainActivity.java.

Если вы работали с библиотеками GUI, которые имеют механизмы генерации дерева UI из декларативного описания интерфейса, то вы должны понимать, что должен быть предоставлен механизм получения ссылок на объекты UI. Система Android, в данном случае, не является исключением - объекты UI создаются объектом LayoutInflater во время работа метода setContentView(). Как же нам получить ссылки на объекты UI?

В Android этот механизм реализован следующим образом. На этапе сборки проекта, с помощью системы сборки создается специальный класс R, который хранит идентификаторы ресурсов, в том числе, пользовательских ресурсов, которые хранятся в папке res. Ко данного класса генерируется автоматически и его не следует редактировать вручную. Фрагмент кода класса представлен ниже

R.java
public final class R {
  public static final class anim {
    public static final int abc_fade_in=0x7f010000;
    public static final int abc_fade_out=0x7f010001;
    public static final int abc_grow_fade_in_from_bottom=0x7f010002;
    public static final int abc_popup_enter=0x7f010003;
    public static final int abc_popup_exit=0x7f010004;
    public static final int abc_shrink_fade_out_from_bottom=0x7f010005;
    public static final int abc_slide_in_bottom=0x7f010006;
    public static final int abc_slide_in_top=0x7f010007;
    public static final int abc_slide_out_bottom=0x7f010008;
    public static final int abc_slide_out_top=0x7f010009;
    public static final int abc_tooltip_enter=0x7f01000a;
    
    ...
    
  }
}

Для каждого типа ресурса предусмотрен свой статический вложенный класс (например, в классе R.string хранятся ссылки на строковые ресурсы, а в классе R.layout хранятся ссылки на все xml-макеты). Структура класса R представлена ниже

Для каждого ресурса определенного типа создается своя статическая целочисленная переменная, которая хранит уникальный числовой идентификатор. Имя переменной совпадает с именем ресурса (либо с названием файла, если в качестве ресурса выступает тот или иной файл).

К примеру, созданные нами в макете activity_main.xml кнопка с идентификатором button и текстовая надпись с идентификатором text также присутствует в этом файле. Так как они маркируется уникальным идентификатором, они находятся в классе R.id:

R.java
public final class R {

  ...
  
  public static final class id {
      ...
      public static final int button=0x7f070022;
      ...
      public static final int text=0x7f070084;
      ...
  }

  ...
}

Чтобы получить ссылку на объекты кнопок и текста, необходимо воспользоваться методом AppCompatActivity.findViewById(). Данный метод осуществляет поиск объекта UI по его идентификатору в дереве элементов, которое мы передали в методе setContentView() и возвращает ссылку на объект, если таковой будет найден. Из описания метода понятно, что мы должны передать методу findViewById() идентификатор элемента, который содержится внутри класса R.

Таким образом, получение ссылки на нашу кнопку и текстовое поле будет выглядеть следующим образом.

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.button);
        TextView text = findViewById(R.id.text);
    }
}

Обратите внимание, что мы вызываем метод findViewById() после вызова метода setContentView(). Подумайте, почему так и что будет, если мы вызовем метод findViewById() до вызова метода setContentView().

Следует отметить, что данный механизм имеет несколько существенных недостатков - необходимо вручную отслеживать тип объекта, который возвращает метод findViewById(), а также не допускать ситуации, когда элемент с таким id не будет найден (это ведет к исключению NPE). В дальнейшем следует использовать механизм привязки данных (Data Binding Library) либо сторонние библиотеки для работы с элементами View (например, Butter Knife).

6. Программирование бизнес-логики

Итак, ссылки на объекты UI получены, теперь реализуем следующую логику приложения - при нажатии на кнопку, содержимое текстовой надписи должно измениться на Hello, Android!.

Сначала мы должны "повесить" слушатель для нашей кнопки. Это можно сделать двумя способами, рассмотрим оба способа.

Способ 1. С помощью атрибута onClick в макете окна

Перейдем в класс MainActivity и создадим метод handeClick(). Метод должен быть публичным. возвращать тип void, а также иметь один входной аргумент - объект типа View (элемент UI, на которое было осуществлено нажатие).

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) { ... }

    public void handleClick(View view) {
    }
}

Далее перейдем в файл макета и отредактируем элемент Button внутри него. Добавим атрибут android:onClick, а в качестве значения укажем метод handleClick.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout ... >

    <TextView
        ... />

    <Button
        android:id="@+id/button"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        android:text="Нажми меня"
        
        android:onClick="handleClick"
        
        app:layout_constraintBottom_toTopOf="@+id/text"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</android.support.constraint.ConstraintLayout>

Всё, теперь мы можем запрограммировать реакцию на нажатие кнопки в теле метода handleClick(). Обратите внимание, что такой способ не требует получения ссылки на объект кнопки.

Способ 2. Вызов метода setOnClickListener() объекта кнопки

Второй способ, традиционный, связан с вызовом метода, которому мы передадим объект слушателя. Для обработки события нажатия на кнопку воспользуемся методом View.setOnClickListener(). Слушатель должен реализовывать интерфейс View.OnClickListener.

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        TextView text = findViewById(R.id.text);
        
        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

            }
        });
    }
}

Для использования лямбда-выражений, зайдем в File -> Project Structure (комбинация клавишCtrl + Alt + Shift + S), после чего в разделе Modules установим поддержку Java версии 8.

Теперь наш код выглядит гораздо чище

MainActivity.java
Button button = findViewById(R.id.button);
button.setOnClickListener(v -> {

});

Итак, мы рассмотрели оба способа, воспользуемся вторым способом и теперь наша задача - изменить содержимое текстового поля на Hello, Android! Для этого вызовем метод setText() у объекта текстового поля и установим нужный текст.

MainActivity.java
TextView text = findViewById(R.id.text);

Button button = findViewById(R.id.button);
button.setOnClickListener(v -> text.setText("Hello, Android!"));

Запустим приложение в эмуляторе и посмотрим на результат

Таким образом, мы разработали простейшее мобильное приложение с использованием элементов UI.

Last updated