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łujeonDraw). Użyj jej, gdy zmienia się zawartość, ale rozmiar pozostaje taki sam.requestLayout()— ponownie mierzy i rysuje widok (wywołuje najpierwonMeasure, a następnieonDraw). 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 = 75fWidoki 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ątrzonDraw()onDraw(canvas)— rysuj okręgi, łuki, tekst i bitmapy- Użyj
invalidate(), aby ponownie narysować widok, orazrequestLayout(), aby ponownie go zmierzyć - Niestandardowe atrybuty za pomocą
attrs.xmldo 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
- Podstawy Material Design
- Motywy i style
- Niestandardowe widoki
- Animacje i przejścia
- Dolna nawigacja i karty