Shadow (umbră) — un efect vizual în interfețele mobile care creează iluzia profunzimii și ierarhiei între elemente. Umbrele arată care element se află mai sus în planul Z și, prin urmare, este mai important sau interactiv. Conform Material Design Guidelines (Google, 2026), umbrele sistemice utilizează cinci înălțimi cheie (elevation): 0 dp (plat), 1–4 dp (butoane), 6–12 dp (carduri), 16–24 dp (dialoguri), 24+ dp (ferestre modale). Mai multe despre înălțimea elementelor citiți în articolul despre Elevation.
Puncte principale
Umbra (shadow) în interfețele utilizator — este un efect vizual care imită iluminarea și profunzimea. Umbra se creează prin desenarea unei copii întunecate estompate a elementului, deplasată față de original. În designul mobil, umbrele îndeplinesc trei funcții: arată ierarhia (elementul mai sus — umbra mai lungă), semnalizează interactivitatea (butoanele cu umbră par apăsabile), separă conținutul de fundal (cardurile cu umbră sunt percepute ca blocuri separate).
În Material Design Google, umbrele sunt indisolubil legate de conceptul de elevation (înălțime). Fiecare element ocupă o anumită înălțime în coordonata Z, iar umbra este proiecția iluminării de sus. Human Interface Guidelines Apple nu prescriu un sistem strict de înălțimi, dar recomandă utilizarea umbrelor pentru a crea ierarhie vizuală și profunzime în interfețe.
Conform cercetării Nielsen Norman Group (2024), umbrele plasate corect reduc timpul de găsire a unui element interactiv cu 24%. Umbrele ajută, de asemenea, utilizatorii cu deficiențe de vedere să distingă limitele elementelor. Cu toate acestea, utilizarea excesivă a umbrelor (3+ niveluri pe ecran) crește sarcina cognitivă și degradează percepția.
Pe iOS umbrele sunt implementate prin proprietățile CALayer: shadowColor setează culoarea umbrei, shadowOffset — deplasarea în CGSize (x, y), shadowRadius — raza de estompare, shadowOpacity — opacitatea de la 0 la 1. Pentru performanță se recomandă și setarea shadowPath — UIBezierPath care descrie forma umbrei, pentru ca Core Animation să nu o calculeze automat.
// UIKit: configurarea umbrei prin CALayer
import UIKit
let cardView = UIView()
cardView.backgroundColor = .systemBackground
cardView.layer.cornerRadius = 12
// Configurarea umbrei
cardView.layer.shadowColor = UIColor.black.cgColor
cardView.layer.shadowOpacity = 0.15
cardView.layer.shadowOffset = CGSize(width: 0, height: 4)
cardView.layer.shadowRadius = 8
"> Optimizare: shadowPath explicit
cardView.layer.shadowPath = UIBezierPath(
roundedRect: cardView.bounds,
cornerRadius: 12
).cgPath
În SwiftUI umbrele se setează prin modificatorul .shadow() cu parametrii color, radius, x, y. SwiftUI aplică automat shadowPath și gestionează performanța randării. Pentru umbre avansate (umbre interioare, umbre multiple) utilizați overlay() cu LinearGradient.
// SwiftUI: umbră prin modificatorul shadow
import SwiftUI
VStack {
Text("Card cu umbră")
.padding()
.background(Color.white)
.cornerRadius(16)
.shadow(
color: .black.opacity(0.15),
radius: 10,
x: 0,
y: 4
)
}
Performanța umbrelor pe iOS: dacă shadowPath nu este setat, Core Animation calculează umbra pe baza formei stratului în fiecare cadru, ceea ce poate cauza întârzieri în timpul animației. Pentru elemente statice setați întotdeauna shadowPath. Pentru elemente animate utilizați shouldRasterize = true pe CALayer pentru a stoca umbra într-un strat separat. În SwiftUI rasterizarea este gestionată prin .drawingGroup().
În Android umbrele în Material Design sunt implementate prin elevation — proprietatea View care setează înălțimea elementului în coordonata Z. Sistemul desenează automat umbra pe baza elevation și outlineProvider (conturul elementului). Cu cât elevation este mai mare, cu atât umbra este mai mare și mai estompată. Pentru View-uri care nu moștenesc Material Design (de exemplu, AppCompat), umbrele funcționează doar cu accelerarea hardware activată.
<!-- XML: elevation prin atribute -->
<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">
<!-- Conținutul cardului -->
</androidx.cardview.widget.CardView>
Pentru umbre personalizate în Android View System se utilizează OutlineProvider. ViewOutlineProvider permite setarea unei forme arbitrare prin outline.setRoundRect() sau outline.setPath(). Fără OutlineProvider umbra este desenată pe baza formei dreptunghiulare a View, ceea ce nu ține cont de rotunjirea colțurilor.
// Kotlin: OutlineProvider personalizat pentru umbră
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 // raza colțului
)
}
}
myView.elevation = 8f // înălțimea umbrei în px
myView.translationZ = 4f // înălțime suplimentară la apăsare
În Jetpack Compose umbrele sunt gestionate prin Modifier.shadow() și elevation în componentele Card și Surface. Modifier.shadow() acceptă elevation și shape. Spre deosebire de View System, Compose desenează umbrele în propriul pipeline de randare, ceea ce oferă un comportament mai predictibil pe toate versiunile de Android.
// Jetpack Compose: umbră prin 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 cu umbră personalizată")
}
Utilizarea corectă a umbrelor îmbunătățește percepția interfeței, utilizarea incorectă — creează zgomot vizual și degradează performanța. Material Design definește cinci niveluri de elevation: 0 dp (fără umbră), 1–4 dp (butoane, chipsuri), 6–12 dp (carduri, meniuri), 16–24 dp (dialoguri, panouri inferioare), 24+ dp (ferestre modale, FAB).
| Nivel | Elevation | Elemente | Caracteristica umbrei |
|---|---|---|---|
| 0 | 0 dp | Text, pictograme, fundal | Fără umbră, strat plat |
| 1 | 1–4 dp | Butoane, câmpuri de intrare | Umbră ușoară, deplasare mică |
| 2 | 6–12 dp | Carduri, Snackbar | Umbră moderată, estompare vizibilă |
| 3 | 16–24 dp | Dialoguri, meniuri | Umbră profundă, estompare mare |
| 4 | 24+ dp | Ferestre modale | Umbră maximă, elementul plutește |
Reguli de bună practică: (1) nu folosiți umbre pentru elemente care nu ar trebui să atragă atenția (fundaluri, separatoare), (2) înălțimea umbrei trebuie să corespundă importanței elementului — cu cât mai important, cu atât mai sus, (3) la apăsarea butonului reduceți elevation animat pentru feedback tactil, (4) evitați umbrele pe fundaluri întunecate — folosiți overlay (luminiere/întunecare) în loc de umbră. În Material You (Android 12+) umbrele se adaptează la temă: pe fundal întunecat elevation este înlocuită cu un strat de culoare (surface tint).
În Flutter umbrele sunt implementate prin BoxShadow în interiorul BoxDecoration. BoxShadow acceptă color, offset, blurRadius și spreadRadius. Flutter suportă umbre multiple: transmiterea unei liste de BoxShadow permite crearea de efecte complexe — de exemplu, umbră externă + iluminare internă. PhysicalModel — o alternativă care simulează o umbră fizică pe baza formei (BeveledRectangleBorder, CircleBorder).
// Flutter: umbră prin 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('Container cu umbră'),
)
// PhysicalModel pentru umbră fizică
PhysicalModel(
color: Colors.white,
elevation: 6.0,
shadowColor: Colors.black.withOpacity(0.2),
borderRadius: BorderRadius.circular(12.0),
child: Text('Model fizic cu umbră'),
)
Performanța: BoxShadow cu umbre multiple poate cauza redesenarea la fiecare modificare (repaint). Pentru elemente statice utilizați PhysicalModel — acesta stochează umbra într-un strat separat (RepaintBoundary). În Flutter 3.16+ a fost adăugat suportul pentru Material 3 elevation cu umbre adaptive care își schimbă culoarea și estomparea în funcție de temă (light/dark).
Întrebări frecvente
Elevation — este înălțimea numerică a elementului în coordonata Z, iar shadow (umbră) — efectul vizual creat de această înălțime. În Android elevation — proprietatea View care generează automat umbra. În design, elevation și shadow sunt adesea folosite ca termeni interschimbabili, dar tehnic elevation este cauza, iar shadow — efectul.
Verificați: (1) View are elevation mai mare de 0, (2) este setat outlineProvider (dacă View nu este din biblioteca Material), (3) accelerarea hardware este activată (android:hardwareAccelerated="true" în manifest). CardView gestionează automat outlineProvider. Pentru View-uri personalizate apelați view.outlineProvider = ViewOutlineProvider.BACKGROUND.
CALayer nu suportă umbre interioare direct. Utilizați o combinație: CAGradientLayer cu culoare neagră și transparență în interiorul măștii (mask), sau suprapuneți un UIImageView cu umbră interioară raster. În SwiftUI umbra interioară se implementează prin overlay cu LinearGradient și blendMode(.multiply).
În Android utilizați translationZ pentru animație: în stare de repaus elevation = 2 dp, la apăsare translationZ = 4 dp (înălțime totală 6 dp). Pe iOS animați shadowOffset și shadowRadius simultan cu transformarea butonului. În SwiftUI utilizați withAnimation cu modificarea elevation prin Modifier.shadow(). În Flutter — AnimatedContainer cu modificarea BoxShadow.
Material Design recomandă nu mai mult de trei niveluri de elevation pe un ecran. Dacă există mai multe elemente cu umbră — combinați-le în grupuri cu aceeași înălțime. Numărul excesiv de umbre creează zgomot vizual și reduce performanța randării pe dispozitivele de segment mediu și bugetar.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și