View & ViewGroup
Save State
View сохраняет и восстанавливает свое состояние с помощью методов onSaveInstanceState
и onRestoreInstanceState
. Чтобы view сохраняло свое состояние при пересоздании activity нужно указать ей id. Если мы создадим 2 EditText с одинаковыми id при повороте экрана восстановится текст из последнего EditText
.
onSizeChanged
Вызывается при изменении размеров View
(смена ориентация устройства, изменение размера родительского контейнера). В этом методе мы выполняем расчеты и подготавливаем данные для отрисовки в onDraw
.
onTouchEvent
Метод обработки событий пользовательского ввода. Вызывается при каждом событии касания View
.
View Lifecycle
• onAttachToWindow
вызывается когда View
прикрепляется к окну.
• onMeasure
вызывается для определения требований к размеру этого View
и всех его дочерних элементов.
• onLayout
вызывается когда View
должно назначить размер и позицию всем своим дочерним элементам.
• onDraw
вызывается когда View должен отрисовать свое содержимое.
• onDetachedFromWindow
вызывается когда View открепляется от своего окна.
ViewGroup
View которая может содержать дочерние view. Базовый класс для FrameLayout
LinearLayout
и других.
onInterceptTouchEvent
Позволяет перехватить ивент во ViewGroup
и не отправлять его вниз по иерархии в таргет-view.
dispatchTouchEvent
Этот метод помогает доставить объект MotionEvent
до View
которой он предназначен.
invalidate
Используется для запроса перерисовки экрана. Когда вызывается этот метод, View помечается как "нуждающаяся в обновлении", что приводит к вызову метода onDraw
в следующем цикле отрисовки, где обновляется её содержимое. Это позволяет обновить отображение View, например, при изменении её состояния или данных.
class CustomView(context: Context): View(context) {
private var color: Int = Color.RED
// Метод для установки нового цвета и запроса перерисовки
fun setColor(newColor: Int) {
color = newColor
invalidate() // Запрашивает перерисовку
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Отрисовываем круг с заданным цветом
canvas.drawColor(color)
}
}
postInvalidate
Используется для запроса перерисовки View из другого потока.
class CustomView(context: Context): View(context) {
private var color: Int = Color.RED
// Метод для установки нового цвета и запроса перерисовки из фонового потока
fun setColorFromBackgroundThread(newColor: Int) {
color = newColor
postInvalidate() // Запрашивает перерисовку из любого потока
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Отрисовываем круг с заданным цветом
canvas.drawColor(color)
}
}
requestLayout
Используется для запроса перерасчета размеров и размещения View. Когда этот метод вызывается, система отмечает View как требующую перерисовки и запускает процесс перерасчета и повторного размещения всех затронутых View. Использовать при изменении размера или содержимого.
class CustomView(context: Context): View(context) {
private var customSize: Int = 100
// Метод для изменения размера и запроса повторного размещения
fun setCustomSize(size: Int) {
if (customSize != size) {
customSize = size
requestLayout() // Запрашивает перерасчет и перераспределение размеров
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// Устанавливаем размеры view
val width = resolveSize(customSize, widthMeasureSpec)
val height = resolveSize(customSize, heightMeasureSpec)
setMeasuredDimension(width, height)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Отрисовка в зависимости от размера
canvas.drawColor(Color.RED)
}
}
forceLayout
Используется для принудительного выполнения перерасчета размеров и размещения View. Этот метод часто применяется в случаях, когда необходимо убедиться, что перерасчет и размещение View происходят немедленно, а не в следующем цикле отрисовки.
class CustomView(context: Context): View(context) {
private var customSize: Int = 100
// Метод для изменения размера и принудительного выполнения перерасчета
fun setCustomSize(size: Int) {
if (customSize != size) {
customSize = size
forceLayout() // Принудительно выполняет перерасчет и размещение
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// Устанавливаем размеры view
val width = resolveSize(customSize, widthMeasureSpec)
val height = resolveSize(customSize, heightMeasureSpec)
setMeasuredDimension(width, height)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// Отрисовка в зависимости от размера
canvas.drawColor(Color.RED)
}
}
onMeasure
Отвечает за вычисление размера View перед его отображением. Он получает параметры widthMeasureSpec и heightMeasureSpec от родительского контейнера и на основе этих параметров определяет размеры View.
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
// Извлекаем режим и размер для ширины
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
// Извлекаем режим и размер для высоты
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
// Пример желаемых размеров
val desiredWidth = 200
val desiredHeight = 100
// Определяем конечные размеры с учетом режимов измерения
val width = if (widthMode == MeasureSpec.EXACTLY) {
widthSize
} else {
Math.min(desiredWidth, widthSize)
}
val height = if (heightMode == MeasureSpec.EXACTLY) {
heightSize
} else {
Math.min(desiredHeight, heightSize)
}
// Устанавливаем измеренные размеры
setMeasuredDimension(width, height)
}
MeasureSpec
Используется для передачи информации о размере и режиме измерения от родительского компонента к дочернему View. Это позволяет дочернему View адаптироваться к доступному пространству.
• EXACTLY
Родительский компонент предоставляет точный размер для View. View должна использовать этот размер и не изменять его. Этот режим устанавливается, когда родительский компонент определяет фиксированный размер для View, например, в случае LayoutParams с точными значениями. При использовании match_parent или layout_width/layout_height, когда размеры явно заданы.
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
if (widthMode == MeasureSpec.EXACTLY) {
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
// Ширина точно задана
}
• AT_MOST
Родительский компонент предоставляет максимальный размер, который View может занять. View должна быть не больше этого размера. Этот режим применяется, когда View имеет неопределенный размер и может быть ограничена только максимальными размерами, предоставленными родителем. При использовании wrap_content, когда View может занимать до определенного максимума, но не больше него.
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
if (widthMode == MeasureSpec.AT_MOST) {
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
// Ширина должна быть не больше максимального значения
}
• UNSPECIFIED
Родительский компонент не ограничивает размер View. View может быть любого размера. Этот режим применяется, когда родитель не устанавливает конкретные ограничения на размер View. View может занять любое пространство, которое она считает нужным. Когда родительский контейнер позволяет View занять любое пространство без ограничений.
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
if (widthMode == MeasureSpec.UNSPECIFIED) {
// Размер не ограничен, `View` может быть любым
}
MeasureSpec.getMode
Определяет режим измерения (EXACTLY, AT_MOST, UNSPECIFIED).
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
}
MeasureSpec.getSize
Получает размер, указанный в measureSpec, который можно использовать для вычислений размеров View.
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
}
onLayout
Bспользуется для определения расположения и размеров дочерних View внутри ViewGroup. Этот метод вызывается после того, как ViewGroup завершил процесс измерения onMeasure и готов перейти к размещению дочерних элементов.
class CustomLayout(context: Context) : ViewGroup(context) {
init {
// Пример добавления дочерних View
addView(View(context))
addView(View(context))
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// Определение размеров для этого ViewGroup
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
setMeasuredDimension(width, height)
// Измерение дочерних View
for (i in 0 until childCount) {
val child = getChildAt(i)
child.measure(
MeasureSpec.makeMeasureSpec(width / 2, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height / childCount, MeasureSpec.EXACTLY)
)
}
}
/**
* changed - логическое значение, указывающее, изменилось ли расположение.
* l, t, r, b - координаты (левый, верхний, правый, нижний) текущего ViewGroup.
*/
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
// Размещение дочерних View
var topOffset = 0
for (i in 0 until childCount) {
val child = getChildAt(i)
val childWidth = child.measuredWidth
val childHeight = child.measuredHeight
child.layout(0, topOffset, childWidth, topOffset + childHeight)
topOffset += childHeight
}
}
}
Android View. Вопросы на собесе
- Что делает метод onMeasure?
Используется для измерения размера View и его дочерних элементов, устанавливая ширину и высоту в соответствии с заданными условиями и ограничениями.
- Что делает метод onLayout?
Отвечает за позиционирование и установку размеров дочерних элементов внутри
ViewGroup
или текущего элемента и его оступов вView
.
- Что делает метод onDraw?
Отвечает за отрисовку содержимого View на экране, где можно использовать
Canvas
для рисования графических элементов.
- Что делает метод invalidate?
Помечает
View
как требующую перерисовки, вызывая методonDraw()
для обновления её внешнего вида.
- Что делает метод requestLayout?
Инициирует перерасчет размеров и перерисовку View, вызывая
onMeasure()
иonLayout()
.
- Какой жизненный цикл у View?
onAttachToWindow()
→onMeasure()
→onLayout()
→onDraw()
→onDetachedFromWindow()
- Какие есть инструменты для создания анимаций?
ValueAnimator
PropertyAnimator
ObjectAnimator
MotionLayout
- Как сохранять состояние View?
• Указать id.
• Переопределить методы
onSaveInstanceState
иonRestoreInstanceState
.• У
View
может быть свояViewModel
.
- Как сделать круглый View?
Сanvas.drawCircle
- Как сделать одинаковое соотношение сторон у View?
ConstraintLayout
сratio
,onMeasure()
вручную изменитьMeasureSpec
- С помощью какого компонента лучше выполнять отложенный (по запросу) inflate дочерней View?
ViewStub
- Какие методы наследника ViewGroup нужно переопределять чтобы управлять его размерами и позиционированием содержимого?
onMeasure()
иonLayout()