Shadow (cień) — efekt wizualny w interfejsach mobilnych, który tworzy iluzję głębi i hierarchii między elementami. Cienie pokazują, który element znajduje się wyżej w płaszczyźnie Z, a zatem jest ważniejszy lub interaktywny. Według Material Design Guidelines (Google, 2026), systemowe cienie wykorzystują pięć kluczowych wysokości (elevation): 0 dp (płaski), 1–4 dp (przyciski), 6–12 dp (karty), 16–24 dp (dialogi), 24+ dp (okna modalne). Więcej o wysokości elementów przeczytasz w artykule o Elevation.
Najważniejsze
cień (shadow) w interfejsach użytkownika — to efekt wizualny imitujący oświetlenie i głębię. Cień tworzy się poprzez rysowanie ciemnej rozmytej kopii elementu przesuniętej względem oryginału. W projektowaniu mobilnym cienie pełnią trzy funkcje: pokazują hierarchię (wyższy element — dłuższy cień), sygnalizują interaktywność (przyciski z cieniem wyglądają na klikalne), oddzielają treść od tła (karty z cieniem są odbierane jako osobne bloki).
W Material Design Google cienie są nierozerwalnie związane z koncepcją elevation (wysokości). Każdy element zajmuje określoną wysokość w współrzędnej Z, a cień to projekcja oświetlenia z góry. Human Interface Guidelines Apple nie nakazują ścisłego systemu wysokości, ale zalecają używanie cieni do tworzenia wizualnej hierarchii i głębi w interfejsach.
Według badań Nielsen Norman Group (2024), prawidłowo rozmieszczone cienie skracają czas znajdowania interaktywnego elementu o 24%. Cienie pomagają również użytkownikom z zaburzeniami wzroku rozróżniać granice elementów. Jednak nadmierne użycie cieni (3+ poziomy na ekranie) zwiększa obciążenie poznawcze i pogarsza percepcję.
Na iOS cienie są implementowane przez właściwości CALayer: shadowColor ustawia kolor cienia, shadowOffset — przesunięcie w CGSize (x, y), shadowRadius — promień rozmycia, shadowOpacity — nieprzezroczystość od 0 do 1. Dla wydajności zaleca się również ustawienie shadowPath — UIBezierPath, który opisuje kształt cienia, aby Core Animation nie obliczała go automatycznie.
// UIKit: konfiguracja cienia przez CALayer
import UIKit
let cardView = UIView()
cardView.backgroundColor = .systemBackground
cardView.layer.cornerRadius = 12
// Konfiguracja cienia
cardView.layer.shadowColor = UIColor.black.cgColor
cardView.layer.shadowOpacity = 0.15
cardView.layer.shadowOffset = CGSize(width: 0, height: 4)
cardView.layer.shadowRadius = 8
"> Optymalizacja: jawny shadowPath
cardView.layer.shadowPath = UIBezierPath(
roundedRect: cardView.bounds,
cornerRadius: 12
).cgPath
W SwiftUI cienie ustawia się przez modyfikator .shadow() z parametrami color, radius, x, y. SwiftUI automatycznie stosuje shadowPath i zarządza wydajnością renderowania. Do zaawansowanych cieni (wewnętrzne cienie, wielokrotne cienie) używaj overlay() z LinearGradient.
// SwiftUI: cień przez modyfikator shadow
import SwiftUI
VStack {
Text("Karta z cieniem")
.padding()
.background(Color.white)
.cornerRadius(16)
.shadow(
color: .black.opacity(0.15),
radius: 10,
x: 0,
y: 4
)
}
Wydajność cieni na iOS: jeśli shadowPath nie jest ustawiony, Core Animation oblicza cień na podstawie kształtu warstwy w każdej klatce, co może powodować spadki płynności podczas animacji. Dla statycznych elementów zawsze ustawiaj shadowPath. Dla animowanych elementów używaj shouldRasterize = true na CALayer, aby zapisać cień w osobnej warstwie. W SwiftUI rasteryzacja jest zarządzana przez .drawingGroup().
W Android cienie w Material Design są implementowane przez elevation — właściwość View, która ustawia wysokość elementu w współrzędnej Z. System automatycznie rysuje cień na podstawie elevation i outlineProvider (konturu elementu). Im wyższe elevation, tym większy i bardziej rozmyty cień. Dla View niedziedziczących po Material Design (np. AppCompat), cienie działają tylko przy włączonym przyspieszeniu sprzętowym.
<!-- XML: elevation przez atrybuty -->
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="12dp"
app:cardElevation="6dp">
<!-- Zawartość karty -->
</androidx.cardview.widget.CardView>
Do niestandardowych cieni w Android View System używa się OutlineProvider. ViewOutlineProvider pozwala ustawić dowolny kształt przez outline.setRoundRect() lub outline.setPath(). Bez OutlineProvider cień jest rysowany w prostokątnym kształcie View, co nie uwzględnia zaokrąglenia rogów.
// Kotlin: niestandardowy OutlineProvider dla cienia
import android.graphics.Outline
import android.view.View
import android.view.ViewOutlineProvider
val myView: View = findViewById(R.id.myView)
myView.outlineProvider = object : ViewOutlineProvider() {
override fun getOutline(view: View, outline: Outline) {
outline.setRoundRect(
0, 0, view.width, view.height,
12f // promień zaokrąglenia
)
}
}
myView.elevation = 8f // wysokość cienia w px
myView.translationZ = 4f // dodatkowa wysokość po naciśnięciu
W Jetpack Compose cienie są zarządzane przez Modifier.shadow() i elevation w komponentach Card i Surface. Modifier.shadow() przyjmuje elevation i shape. W przeciwieństwie do View System, Compose rysuje cienie we własnym potoku renderowania, co daje bardziej przewidywalne zachowanie na wszystkich wersjach Androida.
// Jetpack Compose: cień przez Modifier.shadow
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
Card(
modifier = Modifier.shadow(
elevation = 6.dp,
shape = RoundedCornerShape(12.dp),
clip = false
),
colors = CardDefaults.cardColors(containerColor = Color.White)
) {
Text("Card z niestandardowym cieniem")
}
Prawidłowe używanie cieni poprawia odbiór interfejsu, nieprawidłowe — tworzy wizualny szum i pogarsza wydajność. Material Design określa pięć poziomów elevation: 0 dp (bez cienia), 1–4 dp (przyciski, chipy), 6–12 dp (karty, menu), 16–24 dp (dialogi, arkusze dolne), 24+ dp (okna modalne, FAB).
| Poziom | Elevation | Elementy | Charakterystyka cienia |
|---|---|---|---|
| 0 | 0 dp | Tekst, ikony, tło | Bez cienia, płaska warstwa |
| 1 | 1–4 dp | Przyciski, pola wprowadzania | Lekki cień, niewielkie przesunięcie |
| 2 | 6–12 dp | Karty, Snackbar | Umiarkowany cień, zauważalne rozmycie |
| 3 | 16–24 dp | Dialogi, menu | Głęboki cień, duże rozmycie |
| 4 | 24+ dp | Okna modalne | Maksymalny cień, element unosi się |
Zasady dobrego stylu: (1) nie używaj cieni dla elementów, które nie powinny przyciągać uwagi (tła, separatory), (2) wysokość cienia powinna odpowiadać ważności elementu — im ważniejszy, tym wyższy, (3) po naciśnięciu przycisku zmniejszaj elevation animacyjnie dla dotykowego sprzężenia zwrotnego, (4) unikaj cieni na ciemnych tłach — używaj overlay (rozjaśnienie/przyciemnienie) zamiast cienia. W Material You (Android 12+) cienie dostosowują się do motywu: na ciemnym tle elevation jest zastępowana warstwą koloru (surface tint).
We Flutter cienie są implementowane przez BoxShadow wewnątrz BoxDecoration. BoxShadow przyjmuje color, offset, blurRadius i spreadRadius. Flutter obsługuje wielokrotne cienie: przekazanie listy BoxShadow pozwala tworzyć złożone efekty — na przykład zewnętrzny cień + wewnętrzne podświetlenie. PhysicalModel — alternatywa, która symuluje fizyczny cień na podstawie kształtu (BeveledRectangleBorder, CircleBorder).
// Flutter: cień przez BoxDecoration + BoxShadow
import 'package:flutter/material.dart';
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.0),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.15),
offset: Offset(0, 4),
blurRadius: 8.0,
spreadRadius: 0,
),
],
),
child: Text('Kontener z cieniem'),
)
// PhysicalModel dla fizycznego cienia
PhysicalModel(
color: Colors.white,
elevation: 6.0,
shadowColor: Colors.black.withOpacity(0.2),
borderRadius: BorderRadius.circular(12.0),
child: Text('Model fizyczny z cieniem'),
)
Wydajność: BoxShadow z wieloma cieniami może powodować przerysowanie przy każdej zmianie (repaint). Dla statycznych elementów używaj PhysicalModel — zapisuje on cień w osobnej warstwie (RepaintBoundary). We Flutter 3.16+ dodano obsługę Material 3 elevation z adaptacyjnymi cieniami, które zmieniają kolor i rozmycie w zależności od motywu (light/dark).
Często zadawane pytania
Elevation — to numeryczna wysokość elementu w współrzędnej Z, a shadow (cień) — efekt wizualny tworzony przez tę wysokość. W Android elevation — właściwość View, która automatycznie generuje cień. W projektowaniu elevation i shadow są często używane zamiennie, ale technicznie elevation to przyczyna, a shadow — skutek.
Sprawdź: (1) czy View ma ustawione elevation większe od 0, (2) czy ustawiono outlineProvider (jeśli View nie pochodzi z biblioteki Material), (3) czy włączono przyspieszenie sprzętowe (android:hardwareAccelerated="true" w manifeście). CardView automatycznie zarządza outlineProvider. Dla niestandardowych View wywołaj view.outlineProvider = ViewOutlineProvider.BACKGROUND.
CALayer nie obsługuje wewnętrznych cieni bezpośrednio. Użyj kombinacji: CAGradientLayer z czarnym kolorem i przezroczystością wewnątrz maski (mask), albo nałóż UIImageView z rastrowym wewnętrznym cieniem. W SwiftUI wewnętrzny cień implementuje się przez overlay z LinearGradient i blendMode(.multiply).
W Android używaj translationZ do animacji: w stanie spoczynku elevation = 2 dp, po naciśnięciu translationZ = 4 dp (łączna wysokość 6 dp). Na iOS animuj shadowOffset i shadowRadius jednocześnie z transformacją przycisku. W SwiftUI używaj withAnimation ze zmianą elevation przez Modifier.shadow(). We Flutter — AnimatedContainer ze zmianą BoxShadow.
Material Design zaleca nie więcej niż trzy poziomy elevation na jednym ekranie. Jeśli elementów z cieniem jest więcej — łącz je w grupy o tej samej wysokości. Nadmierna liczba cieni tworzy wizualny szum i obniża wydajność renderowania na urządzeniach średniej i budżetowej klasy.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również