Цикл жизни View

  1. onAttachedToWindow(): Этот метод вызывается, когда View прикрепляется к окну. Это происходит после того, как View и все ее дочерние элементы были измерены и размещены на экране. В этот момент можно выполнять инициализацию, которая требует доступа к размерам и положению View.

  2. onDetachedFromWindow(): Вызывается, когда View открепляется от окна. Это может произойти, когда View удаляется из иерархии представлений или когда ее активность становится невидимой. Здесь вы можете выполнить очистку ресурсов или отмену анимаций, связанных с этим View.

  3. onMeasure(): Этот метод вызывается, когда система определяет размеры View. В этом методе View должна указать желаемые размеры с помощью метода setMeasuredDimension(). Обычно это переопределяется, чтобы правильно измерить размеры View в соответствии с ее содержимым и параметрами разметки.

  4. onLayout(): Вызывается, когда View должна определить свое положение и размер на экране. В этом методе вы должны установить положение и размеры своих дочерних элементов с помощью методов layout() или layoutParams.

  5. onDraw(): Этот метод вызывается при отрисовке View на экране. В этом методе вы можете рисовать содержимое View с помощью Canvas и Paint.

onAttachedToWindow

Метод вызывается когда View приаттачена к Window. В этой фазе вьюшка знает что она может быть активна, следовательно может начаться выделение ресурсов и настройка Listener'ов.

onMeasure

  • Определение размеров виджета: Метод onMeasure позволяет установить размеры виджета в соответствии с его содержимым и параметрами макета. Это включает вычисление ширины и высоты, которые должны занимать виджет на экране.
  • Управление размерами дочерних элементов: Если виджет содержит другие дочерние элементы (например, в ViewGroup), onMeasure используется для измерения размеров этих дочерних элементов и их расположения в пределах родительского виджета.
  • Учет ограничений макета: Метод onMeasure учитывает ограничения, наложенные на виджет через его параметры макета (например, wrap_content, match_parent или конкретные размеры). Он должен обеспечить, чтобы размеры виджета соответствовали этим ограничениям.
  • Адаптация к размерам экрана и устройства: onMeasure также учитывает текущий размер экрана и другие факторы, которые могут влиять на размеры и позиционирование виджета, чтобы обеспечить корректное отображение на различных устройствах и в различных условиях.
  • Подготовка к отрисовке: После завершения работы onMeasure, система Android использует полученные измерения для определения точного размера и расположения виджета перед его фактической отрисовкой (в методе onLayout и последующей onDraw).

onMeasure() не возвращает никаких значение, необходимо вызвать метод setMeasuredDimension() для установки размеров.

onLayout

Вызывается после измерения Views для расположения их на экране

onDraw

Размеры и расположения вычислены на на предыдущих шагах, так что View может быть отрисована на их основе. В методе onDraw(canvas: Canvas) созданный объект Canvas отправляет команды OpenGL-ES в графический процессор.

Важно: нельзя создавать какие-либо объекты в этом методе, потому что метод onDraw может быть вызван несколько раз.

Применить изменения

invalidate

Метод который настаивает на принудительной перерисовке View, изменения в котором мы хотим показать.

Когда вызывается метод invalidate(), Android Framework помечает область, занимаемую этим представлением, как недействительную (требующую перерисовки). Это приводит к вызову метода onDraw() этого представления при следующей перерисовке экрана. Таким образом, представление будет перерисовано, отображая любые внесенные изменения в его внешний вид.

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

requestLayout

Нужно вызвать когда требует повторное вычисление фаз измерения и компоновки View (measure → layout → draw). Простыми словами, requestLayout() следует вызывать, когда происходит изменение границ представления.

Когда вызывается метод requestLayout(), система представлений помечает представление как недействительное в отношении его размеров и макета. Это приводит к повторному запуску цикла измерения и компоновки представлений для этого представления и его родителей.

Например, если размеры представления изменяются (например, ширина или высота), вызов requestLayout() необходим, чтобы уведомить систему представлений о необходимости пересчета макета и размеров представления.

MeasureSpec

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

Существует три возможных режима:

  1. MeasureSpec.EXACTLY (Точно): Родительское представление устанавливает точный размер для дочернего. Дочернему представлению будут предоставлены эти границы независимо от его собственных размеров. Экземпляры, указывающие фиксированную ширину для представления, весы в LinearLayout или атрибут match_parent и т. д., используют этот режим.

  2. MeasureSpec.AT_MOST (Не более): Дочернее представление может быть таким большим, как ему нужно, но не больше указанного размера.

  3. MeasureSpec.UNSPECIFIED (Неопределенный): Родитель не накладывает никаких ограничений на размер дочернего представления. Оно может быть любого размера, какое захочет.

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

import android.view.View
import android.view.View.MeasureSpec

fun main() {
    val parentWidth = 200 // Ширина родительского представления
    val parentHeight = 300 // Высота родительского представления
    
    val childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(parentWidth, MeasureSpec.EXACTLY)
    val childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(parentHeight, MeasureSpec.AT_MOST)
    
    // Создаем дочернее представление
    val childView = View(null)
    
    // Измеряем дочернее представление с использованием MeasureSpec
    childView.measure(childWidthMeasureSpec, childHeightMeasureSpec)
    
    // Получаем измеренные размеры дочернего представления
    val measuredWidth = childView.measuredWidth
    val measuredHeight = childView.measuredHeight
    
    println("Measured Width: $measuredWidth")
    println("Measured Height: $measuredHeight")
}

В этом примере создается дочернее представление, которое измеряется с помощью MeasureSpec. Ширина родительского представления установлена как точное значение (EXACTLY), а высота - как не более указанного размера (AT_MOST). Результаты измерений сохраняются в переменных measuredWidth и measuredHeight.

Пример создания View

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View

class CustomView : View {

    // Переменные для хранения значений пользовательских атрибутов
    private var customColor: Int = Color.BLACK
    private var customText: String = "Hello, World!"

    // Переменные для рисования текста
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)

    constructor(context: Context) : super(context) {
        init(null)
    }

    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
        init(attrs)
    }

    constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) {
        init(attrs)
    }

    // Метод для инициализации атрибутов
    private fun init(attrs: AttributeSet?) {
        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView)

        // Получаем значения пользовательских атрибутов
        customColor = typedArray.getColor(R.styleable.CustomView_customColor, Color.BLACK)
        customText = typedArray.getString(R.styleable.CustomView_customText) ?: "Hello, World!"

        // Освобождаем ресурсы
        typedArray.recycle()

        // Устанавливаем параметры рисования текста
        paint.color = customColor
        paint.textSize = 50f
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        // Рисуем текст в центре представления
        val x = width / 2f - paint.measureText(customText) / 2f
        val y = height / 2f + paint.textSize / 2f
        canvas.drawText(customText, x, y, paint)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        // Рассчитываем размеры представления
        val desiredWidth = suggestedMinimumWidth + paddingLeft + paddingRight
        val desiredHeight = suggestedMinimumHeight + paddingTop + paddingBottom
        val width = resolveSize(desiredWidth, widthMeasureSpec)
        val height = resolveSize(desiredHeight, heightMeasureSpec)
        setMeasuredDimension(width, height)
    }
}