В этом уроке мы познакомимся с основными UI элементами Android. Научимся располагать элементы и сверстаем свой первый экран.
View это класс, который представляет собой базовый элемент UI. View занимает прямоугольную область экрана и отвечает за отрисовку этой области и обработку событий. Все визуальные компоненты наследуются от View.
Как думаете, сколько различных View находится на этом экране?

На экране присутствуют 7 различных View:

Существует два способа объявления UI в Android:
При помощи XML можно быстро и просто создавать UI своего приложения. Для этого необходимо создать новый файл с расширением .xml в каталоге res/layout/. Например, такой:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</FrameLayout>
Для использования этого ресурса, вам необходимо загрузить его в коде приложения. Для Activity это метод onCreate(). Например, если ваш файл называется example.xml, загрузить его можно следующим способом:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.string.example)
}
Этот же макет можно было создать при помощи кода следующим образом:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val frameLayout = FrameLayout(this)
val textView = TextView(this)
textView.layoutParams =
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
)
textView.text = "Hello World"
frameLayout.addView(textView)
setContentView(frameLayout)
}
Каждый объект View поддерживает атрибуты XML. Некоторые атрибуты характерны только для конкретного типа View (например, объект TextView поддерживает атрибут textSize), однако эти атрибуты также наследуются любыми объектами View, которые могут наследовать этот класс. Некоторые атрибуты являются общими для всех объектов View, поскольку они наследуются от корневого класса View (такие, как атрибут id)
Атрибут id
У любого объекта View может быть связанный с ним целочисленный идентификатор, который служит для обозначения уникальности объекта View в иерархии. Во время компиляции приложения этот идентификатор используется как целое число, однако идентификатор обычно назначается в файле XML макета в виде строки в атрибуте id.
android:id="@+id/my_button"
Символ @ в начале строки указывает на то, что обработчику XML следует выполнить синтаксический анализ остальной части идентификатора и определить ее в качестве ресурса идентификатора. Символ плюса (+) обозначает, что это имя нового ресурса, который необходимо создать и добавить к нашим ресурсам (в файле R.java).
Атрибуты layout_width, layout_height
Все View имеют атрибуты ширины и высоты layout_width и layout_height, они могут принимать следующие значения:
wrap_content. Размер представления задается по размерам его содержимого.<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FF0000"
android:text="Hello World!"
android:textSize="40sp" />
</FrameLayout>

match_parent. Размер представления определяется размером его родителя.<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF0000"
android:text="Hello World!"
android:textSize="40sp" />
</FrameLayout>

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="50dp"
android:layout_height="300dp"
android:background="#FF0000"
android:text="Hello World!"
android:textSize="40sp" />
</FrameLayout>

В примерах выше мы использовали атрибуты background и textSize для определения цвета фона и размера текста.
Для определения фиксированного значения мы использовали единицы измерения dp. Density-independent Pixels - это единица измерения учитывающая размер экрана. Различные экраны имеют различное количество пикселей на экране.

dp можно определить, как масштабируемый px. За степень масштабируемости отвечает Screen Density. Это коэффициент, который использует система для вычисления значения dp:

Т.е. когда экран имеет режим mdpi, 1dp = 1px. View шириной 100dp будет выглядеть так же как и View шириной 100px.
Если, например, у нас экран с низким разрешением, то используется режим ldpi. В этом случае 1dp = 0.75px. View шириной 100dp будет выглядеть так же как View шириной 75px.
Для шрифтов используется sp - Scale-independent Pixels. Работает аналогично dp, но для шрифтов.
ImageView - это компонент, который предназначен для отображения изображений. Для того, что бы установить изображение, необходимо задать значение аргументу src.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/cat" />
</FrameLayout>

Для того, что бы управлять масштабированием изображения внутри ImageView, существует атрибут scaleType, меняя его значения можно добиться следующих результатов:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/cat" />
</FrameLayout>

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="center"
android:src="@drawable/cat" />
</FrameLayout>

С отображением компонентов по отдельности мы разобрались, давайте теперь разберемся с тем, как отобразить несколько View на экране.
ViewGroup это наследник класса View, который может хранить в себе другие компоненты.
Базовый ViewGroup называется (root). Для компонентов, которые он хранит в себе он называется родителем (parent) а компоненты по отношению к нему - потомки (child).
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/cat" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="40sp" />
</FrameLayout>

В данном примере FrameLayout является базовым элементом, для него TextView и ImageView являются потомками, а он для них является родителем.
Мы использовали FrameLayout для того, что бы отобразить две View на одном экране. FrameLayout это самый простой способ разметки, в нем объекты накладываются друг на друга. Положением объектов можно управлять только при помощи атрибута gravity.
LinearLayout это более сложный ViewGroup, который выстраивает элементы друг за другом.
Он может быть вертикальным и горизонтальным. За направление отображения отвечает атрибут orientation.
LinearLayout<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="40sp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@drawable/cat" />
</LinearLayout>

LinearLayout<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:textSize="40sp" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scaleType="centerCrop"
android:src="@drawable/cat" />
</LinearLayout>

Для управления местом, занимаемым каждым элементом, используется атрибут layout_weight. Этот атрибут указывает “значимость” элемента. Чем больше значение, тем более высокий приоритет будет отдан элементу, при расчетах размера View.
Равный вес для всех элементов:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:text="Hello World!"
android:textSize="40sp" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:scaleType="centerCrop"
android:src="@drawable/cat" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:text="Hello World!"
android:textSize="40sp" />
</LinearLayout>

Почему изображение все равно занимает больше места? Из-за того что у нас стоит layout_width="wrap_content" TextView может ужиматься. Для того что бы не допустить этого, нужно установить ширину равную 0dp. Такое значение указывает, что элементу стоит занимать все свободное место, что ему доступно.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:text="Hello World!"
android:textSize="40sp" />
<ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:scaleType="centerCrop"
android:src="@drawable/cat" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1.0"
android:text="Hello World!"
android:textSize="40sp" />
</LinearLayout>

Установим изображению приоритет в 5 раз меньше чем для текстовых полей:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="10.0"
android:text="Hello World!"
android:textSize="40sp" />
<ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2.0"
android:scaleType="centerCrop"
android:src="@drawable/cat" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="10.0"
android:text="Hello World!"
android:textSize="40sp" />
</LinearLayout>

Для более удобного управления можно использовать атрибут weightSum. Если установить его равным 100, то можно присваивать веса в процентном соотношении.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:weightSum="100">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="25"
android:text="Hello World!"
android:textSize="40sp" />
<ImageView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="50"
android:scaleType="centerCrop"
android:src="@drawable/cat" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="25"
android:text="Hello World!"
android:textSize="40sp" />
</LinearLayout>

Что если нам необходимо расположить элементы и по вертикали и по горизонтали? Для этого можно использовать несколько LinerLayout с разной ориентаций, но это не всегда удобно и не очень оптимально. Для более сложных интерфейсов используется RelativeLayout
RelativeLayout позволяет расставить элементы относительно друг друга.
Для того, чтобы разместить компонент относительно родителя используются следующие атрибуты:
layout_alignParentBottom - выравнивание относительно нижнего края родителя;layout_alignParentLeft - выравнивание относительно левого края родителя;layout_alignParentRight - выравнивание относительно правого края родителя;layout_alignParentTop - выравнивание относительно верхнего края родителя;layout_centerInParent - выравнивание по центру родителя по вертикали и горизонтали;layout_centerHorizontal - выравнивание по центру родителя по горизонтали;layout_centerVertical - выравнивание по центру родителя по вертикали.Для того, чтобы разместить компонент относительно другого компонента используются следующие атрибуты:
layout_above - размещается над указанным компонентом;layout_below - размещается под указанным компонентом;layout_alignLeft - выравнивается по левому краю указанного компонента;layout_alignRight - выравнивается по правому краю указанного компонента;layout_alignTop - выравнивается по верхнему краю указанного компонента;layout_alignBottom - выравнивается по нижнему краю указанного компонента;layout_toLeftOf - правый край компонента размещается слева от указанного компонента;layout_toRightOf - левый край компонент размещается справа от указанного компонента.Разместим два TextView вверху родителя и внизу родителя:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="First TextView"
android:textSize="40sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="Second TextView"
android:textSize="40sp" />
</RelativeLayout>

Атрибуты можно комбинировать. Разместим второй TextView относительно правого нижнего угла первого TextView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/first_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="First TextView"
android:textSize="40sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/first_view"
android:layout_toRightOf="@+id/first_view"
android:text="Second TextView"
android:textSize="40sp" />
</RelativeLayout>

В примере выше мы добавили id для верхнего TextView, для того, чтобы можно было указать отношение.
Мы научились располагать элементы относительно друг друга, но как сделать так, чтобы они не “слипались.
Для этого существую два атрибута: padding и layout_margin.
padding указывает отступ внутри элемента.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/first_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="#FF0000"
android:text="First TextView"
android:textSize="40sp"
android:padding="100dp"/>
</RelativeLayout>

layout_margin указывает отступ вокруг элемента.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/first_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:background="#FF0000"
android:text="First TextView"
android:textSize="40sp"
android:layout_margin="100dp"/>
</RelativeLayout>

Отступы можно указывать по отдельности для каждой из сторон. Разместим ImageView и рядом два TextView друг под другом:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="20dp"
android:layout_marginTop="20dp"
android:scaleType="centerCrop"
android:src="@drawable/cat" />
<TextView
android:id="@+id/title_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/image_view"
android:layout_marginBottom="20dp"
android:layout_toRightOf="@+id/image_view"
android:background="#FF0000"
android:text="Title"
android:textSize="40sp" />
<TextView
android:id="@+id/subtitle_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/title_view"
android:layout_toRightOf="@+id/image_view"
android:background="#FFFF00"
android:text="SubTitle"
android:textSize="30sp" />
</RelativeLayout>

При помощи атрибута gravity можно управлять положением контента внутри элемента. Например, разместить текст в нижней части TextView.
Атрибут layout_gravity позволяет указать положение самого View внутри родителя.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="bottom">
<TextView
android:layout_width="300dp"
android:layout_height="200dp"
android:background="#FF0000"
android:gravity="bottom|right"
android:text="First TextView"
android:textSize="40sp" />
<TextView
android:layout_width="300dp"
android:layout_height="200dp"
android:layout_gravity="bottom"
android:background="#FFFF00"
android:gravity="top"
android:text="Second TextView"
android:textSize="40sp" />
</FrameLayout>

Для того, что бы разместить текст в нижнем правом углу первого TextView мы скомбинировали значение bottom и right.