0Pricing
Android Academy · Lekcja

Niestandardowe widoki

Rysuj niestandardowe komponenty interfejsu od podstaw. Rozszerz View, zaimplementuj onMeasure i onDraw za pomocą Canvas i Paint, zdefiniuj niestandardowe atrybuty XML i twórz widoki złożone.

Niestandardowe widoki to bezpłatna lekcja Android Academy na CoddyKit. To lekcja 3 z 5. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Android Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Android Academy zawiera 5 lekcji w sumie.

Dlaczego warto tworzyć niestandardowe widoki?

Czasami wbudowane widoki Androida nie zapewniają dokładnie tego, czego potrzebujesz. Niestandardowe widoki pozwalają:

  • Rysować kształty, wykresy, pierścienie postępu lub diagramy, których nie ma w SDK
  • Tworzyć markowe komponenty interfejsu używane spójnie w całej aplikacji
  • Łączyć kilka widoków w jeden komponent wielokrotnego użytku (Compound View)
  • Osiągać wyższą wydajność dzięki eliminowaniu zagnieżdżonych hierarchii układów

Dziedziczenie po View

Utwórz niestandardowy widok, dziedzicząc po View (lub podklasie, takiej jak ImageView). Przesłoń wymagany konstruktor:

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() informuje element nadrzędny, jak duży powinien być ten widok. Na końcu zawsze wywołuj 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 — narzędzie do rysowania

Obiekt Paint przechowuje informacje o stylu rysowania (kolor, szerokość obrysu, wygładzanie krawędzi). Utwórz go raz — nigdy wewnątrz 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() — rysowanie widoku

onDraw(canvas) jest wywoływana za każdym razem, gdy widok wymaga ponownego narysowania. Użyj obiektu Canvas do rysowania kształtów, tekstu i bitmap:

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() a requestLayout()

Dwie metody wyzwalające aktualizację:

  • invalidate() — ponownie rysuje widok (wywołuje onDraw). Użyj jej, gdy zmienia się zawartość, ale rozmiar pozostaje taki sam.
  • requestLayout() — ponownie mierzy i rysuje widok (wywołuje najpierw onMeasure, a następnie onDraw). Użyj jej, gdy zmienia się rozmiar widoku.

Niestandardowe atrybuty (attrs.xml)

Zadeklaruj niestandardowe atrybuty XML, aby można było konfigurować widok z poziomu plików układu:

<!-- 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>

Odczytywanie niestandardowych atrybutów

Odczytaj atrybuty w konstruktorze za pomocą 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)
    }
}

Używanie niestandardowego widoku w XML

Użyj niestandardowego widoku w układzie XML, podając pełną ścieżkę klasy i niestandardowe atrybuty:

<!-- 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

Widoki złożone

Widok złożony łączy istniejące widoki w jeden komponent wielokrotnego użytku. Dziedzicz po klasie układu, takiej jak 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 }
}

Rysowanie tekstu

Rysuj tekst na obiekcie Canvas za pomocą drawText(). Wyśrodkuj go precyzyjnie, używając 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)
}

Szybkie sprawdzenie

Jaką metodę należy wywołać, aby ponownie narysować niestandardowy widok, gdy zmieniają się dane, ale rozmiar pozostaje taki sam?

Podsumowanie: niestandardowe widoki

Możesz teraz narysować dowolny element na ekranie:

  • Dziedzicz po View, używając @JvmOverloads constructor
  • onMeasure() — zgłoś żądany rozmiar za pomocą setMeasuredDimension()
  • Paint — definiuje styl; utwórz go raz, nie wewnątrz onDraw()
  • onDraw(canvas) — rysuj okręgi, łuki, tekst i bitmapy
  • Użyj invalidate(), aby ponownie narysować widok, oraz requestLayout(), aby ponownie go zmierzyć
  • Niestandardowe atrybuty za pomocą attrs.xml do konfiguracji XML
  • Widok złożony — dziedzicz po układzie, aby łączyć istniejące widoki

Dalej: ożywianie interfejsu za pomocą animacji i przejść.

Często zadawane pytania

Czy lekcja „Niestandardowe widoki” jest bezpłatna?

Tak — pełny tekst „Niestandardowe widoki” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Android Academy, przejdź na CoddyKit PRO. Kurs Android Academy zawiera 5 lekcji w sumie.

Co nauczysz się w „Niestandardowe widoki”?

Rysuj niestandardowe komponenty interfejsu od podstaw. Rozszerz View, zaimplementuj onMeasure i onDraw za pomocą Canvas i Paint, zdefiniuj niestandardowe atrybuty XML i twórz widoki złożone. Ćwiczysz Android Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Android Academy?

Nie wymagamy żadnego doświadczenia. Android Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 5.

Ile czasu zajmuje lekcja „Niestandardowe widoki”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Android Academy?

Tak. Każda lekcja Android Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Podstawy Material Design
  2. Motywy i style
  3. Niestandardowe widoki
  4. Animacje i przejścia
  5. Dolna nawigacja i karty
← Powrót do Android Academy