0Pricing
Android Academy · Урок

Пользовательские представления

Создавайте пользовательские компоненты интерфейса с нуля. Расширяйте View, реализуйте onMeasure и onDraw с помощью Canvas и Paint, определяйте пользовательские XML-атрибуты и создавайте составные представления.

«Пользовательские представления» — бесплатный урок Android Academy на CoddyKit. Это урок 3 из 5. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Android Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Android Academy содержит 5 уроков всего.

Зачем нужны пользовательские представления

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

  • Рисовать фигуры, графики, индикаторы прогресса или диаграммы, которых нет в SDK
  • Создавать фирменные компоненты интерфейса, единообразно используемые во всём приложении
  • Объединять несколько представлений в один повторно используемый компонент (составное представление)
  • Повышать производительность за счёт устранения вложенных иерархий макетов

Расширение View

Создайте пользовательское представление, расширив View (или подкласс, например ImageView). Переопределите необходимый конструктор:

class CircleProgressView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {

    var progress: Float = 0f
        set(value) {
            field = value.coerceIn(0f, 100f)
            invalidate()   // trigger redraw
        }

    // onMeasure and onDraw go here
}

onMeasure()

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

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    val desiredSize = 200  // dp → pixels
    val px = (desiredSize * resources.displayMetrics.density).toInt()

    val width  = resolveSize(px, widthMeasureSpec)
    val height = resolveSize(px, heightMeasureSpec)

    // Square: same width and height
    val size = minOf(width, height)
    setMeasuredDimension(size, size)
}

Paint — инструмент рисования

Объект Paint хранит параметры стиля рисования (цвет, ширину обводки, сглаживание). Создайте его один раз — никогда не создавайте внутри onDraw():

class CircleProgressView ... : View(...) {

    // Create Paint in init, not in onDraw!
    private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.LTGRAY
        style = Paint.Style.STROKE
        strokeWidth = 20f
        strokeCap = Paint.Cap.ROUND
    }

    private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        color = Color.parseColor("#6750A4")
        style = Paint.Style.STROKE
        strokeWidth = 20f
        strokeCap = Paint.Cap.ROUND
    }
}

onDraw() — рисование представления

onDraw(canvas) вызывается каждый раз, когда представление нужно перерисовать. Используйте Canvas для рисования фигур, текста и растровых изображений:

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    val cx = width / 2f
    val cy = height / 2f
    val radius = (minOf(width, height) / 2f) - 30f

    // Draw background circle
    canvas.drawCircle(cx, cy, radius, backgroundPaint)

    // Draw progress arc
    val oval = RectF(cx - radius, cy - radius, cx + radius, cy + radius)
    val sweepAngle = 360f * (progress / 100f)
    canvas.drawArc(oval, -90f, sweepAngle, false, progressPaint)
}

invalidate() и requestLayout()

Два метода для запуска обновления:

  • invalidate() — перерисовывает представление (вызывает onDraw). Используйте, когда содержимое изменилось, но размер остался прежним.
  • requestLayout() — заново измеряет и перерисовывает представление (вызывает сначала onMeasure, затем onDraw). Используйте, когда размер представления изменился.

Пользовательские атрибуты (attrs.xml)

Объявите пользовательские XML-атрибуты, чтобы настраивать представление из файлов макетов:

<!-- res/values/attrs.xml -->
<resources>
    <declare-styleable name="CircleProgressView">
        <attr name="progressColor" format="color" />
        <attr name="trackColor"    format="color" />
        <attr name="strokeWidth"   format="dimension" />
        <attr name="progress"      format="float" />
    </declare-styleable>
</resources>

Чтение пользовательских атрибутов

Считывайте attrs в конструкторе с помощью obtainStyledAttributes:

init {
    context.obtainStyledAttributes(attrs, R.styleable.CircleProgressView).use { ta ->
        progressPaint.color = ta.getColor(
            R.styleable.CircleProgressView_progressColor,
            Color.parseColor("#6750A4")
        )
        backgroundPaint.color = ta.getColor(
            R.styleable.CircleProgressView_trackColor,
            Color.LTGRAY
        )
        val sw = ta.getDimension(R.styleable.CircleProgressView_strokeWidth, 20f)
        progressPaint.strokeWidth = sw
        backgroundPaint.strokeWidth = sw
        progress = ta.getFloat(R.styleable.CircleProgressView_progress, 0f)
    }
}

Использование пользовательского представления в XML

Используйте пользовательское представление в XML-макете, указав полный путь к классу и пользовательские атрибуты:

<!-- In any layout XML -->
<com.example.myapp.CircleProgressView
    android:id="@+id/progressView"
    android:layout_width="120dp"
    android:layout_height="120dp"
    app:progress="65"
    app:progressColor="@color/primary"
    app:trackColor="@color/surface_variant"
    app:strokeWidth="12dp" />

<!-- Control in code: -->
binding.progressView.progress = 75f

Составные представления

Составное представление объединяет существующие представления в один повторно используемый компонент. Расширьте класс макета, например LinearLayout:

class SearchBarView @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : LinearLayout(context, attrs) {

    private val binding = ViewSearchBarBinding.inflate(LayoutInflater.from(context), this)

    init {
        orientation = HORIZONTAL
        binding.ivSearch.setOnClickListener {
            onSearchClick?.invoke(binding.etSearch.text.toString())
        }
    }

    var onSearchClick: ((String) -> Unit)? = null

    fun setHint(hint: String) { binding.etSearch.hint = hint }
}

Рисование текста

Рисуйте текст на Canvas с помощью drawText(). Точно выравнивайте его по центру с помощью Paint.getFontMetrics():

private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
    color = Color.BLACK
    textSize = 48f
    textAlign = Paint.Align.CENTER
}

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    val cx = width / 2f
    val cy = height / 2f

    // Vertically center the text:
    val fm = textPaint.fontMetrics
    val textY = cy - (fm.ascent + fm.descent) / 2f
    canvas.drawText("${progress.toInt()}%", cx, textY, textPaint)
}

Быстрая проверка

Какой метод следует вызвать, чтобы перерисовать пользовательское представление, если данные изменились, но размер остался прежним?

Повторение: пользовательские представления

Теперь вы умеете рисовать на экране что угодно:

  • Расширяйте View с помощью @JvmOverloads constructor
  • onMeasure() — сообщайте требуемый размер с помощью setMeasuredDimension()
  • Paint — задавайте стиль; создавайте один раз, а не внутри onDraw()
  • onDraw(canvas) — рисуйте круги, дуги, текст и растровые изображения
  • Используйте invalidate() для перерисовки, а requestLayout() — для повторного измерения
  • Пользовательские атрибуты через attrs.xml для настройки из XML
  • Составное представление — расширяйте макет, чтобы объединять существующие представления

Далее: оживите интерфейс с помощью анимаций и переходов.

Часто задаваемые вопросы

Урок «Пользовательские представления» бесплатный?

Да — полный текст урока «Пользовательские представления» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Android Academy, подпишись на CoddyKit PRO. Курс Android Academy содержит 5 уроков всего.

Чему я научусь в уроке «Пользовательские представления»?

Создавайте пользовательские компоненты интерфейса с нуля. Расширяйте View, реализуйте onMeasure и onDraw с помощью Canvas и Paint, определяйте пользовательские XML-атрибуты и создавайте составные пре… Ты практикуешь Android Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Android Academy?

Предыдущий опыт не требуется. Android Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 5.

Сколько времени занимает урок «Пользовательские представления»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Android Academy?

Да. Каждый урок Android Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Основы Material Design
  2. Темы и стили
  3. Пользовательские представления
  4. Анимации и переходы
  5. Нижняя навигация и вкладки
← Назад к Android Academy