Shadow: istota, tworzenie cieni w aplikacjach mobilnych

Autor: IT Sectr Opublikowano: 2026-02-28 Czas czytania: 9 min

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) — efekt wizualny tworzący głębię poprzez przyciemnienie obszaru pod elementem z rozmyciem na krawędziach.
  • Na iOS cienie ustawia się przez właściwości CALayer: shadowColor, shadowOffset, shadowRadius, shadowOpacity.
  • W Android cienie są zarządzane przez elevation (Material Design) lub niestandardowe renderowanie przez OutlineProvider.
  • Jetpack Compose obsługuje cienie przez Modifier.shadow() i elevation w Card/Surface.
  • We Flutter cienie są implementowane przez BoxShadow w BoxDecoration lub przez PhysicalModel.

Czym jest cień w UI?

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ę.

Cienie na iOS: CALayer i SwiftUI

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.

swift
// 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.

swift
// 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().

Cienie w Android: elevation i niestandardowe cienie

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

kotlin
// 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")
}

Zalecenia dotyczące używania cieni

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).

PoziomElevationElementyCharakterystyka cienia
00 dpTekst, ikony, tłoBez cienia, płaska warstwa
11–4 dpPrzyciski, pola wprowadzaniaLekki cień, niewielkie przesunięcie
26–12 dpKarty, SnackbarUmiarkowany cień, zauważalne rozmycie
316–24 dpDialogi, menuGłęboki cień, duże rozmycie
424+ dpOkna modalneMaksymalny 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).

Cienie we Flutter: BoxShadow i PhysicalModel

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).

dart
// 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

Czym elevation różni się od cienia?

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.

Dlaczego cień nie wyświetla się na Android?

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.

Jak zrobić wewnętrzny cień (inner shadow) na iOS?

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).

Jak animować cień po naciśnięciu przycisku?

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.

Ile cieni można używać na jednym ekranie?

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

  • cień (shadow) — efekt wizualny głębi oparty na elevation elementu w współrzędnej Z.
  • Na iOS cienie konfiguruje się przez CALayer: shadowColor, shadowOffset, shadowRadius, shadowOpacity.
  • W Android cienie są zarządzane przez elevation i OutlineProvider dla niestandardowego kształtu.
  • Material Design określa pięć poziomów elevation: 0, 1–4, 6–12, 16–24 i 24+ dp.
  • Jetpack Compose używa Modifier.shadow() i elevation w Card/Surface do zarządzania cieniami.
  • Flutter implementuje cienie przez BoxShadow (BoxDecoration) i PhysicalModel (fizyczny cień).
  • Optymalizacja: ustawiaj shadowPath (iOS), outlineProvider (Android), używaj PhysicalModel (Flutter).

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.

Omów projekt

Przeczytaj również