Shadow: essenza, creazione di ombre nelle applicazioni mobili

Autore: IT Sectr Pubblicato: 2026-02-28 Tempo di lettura: 9 min

Shadow (ombra) — un effetto visivo nelle interfacce mobili che crea l'illusione di profondità e gerarchia tra gli elementi. Le ombre mostrano quale elemento si trova più in alto nel piano z e, quindi, è più importante o interattivo. Secondo le Material Design Guidelines (Google, 2026), le ombre di sistema utilizzano cinque altezze chiave (elevation): 0 dp (piatto), 1–4 dp (pulsanti), 6–12 dp (carte), 16–24 dp (dialoghi), 24+ dp (finestre modali). Maggiori informazioni sull'altezza degli elementi nell'articolo su Elevation.

Punti principali

  • Ombra (shadow) — effetto visivo che crea profondità scurendo l'area sotto un elemento con sfocatura dei bordi.
  • Su iOS, le ombre vengono impostate tramite le proprietà di CALayer: shadowColor, shadowOffset, shadowRadius, shadowOpacity.
  • In Android, le ombre vengono gestite tramite elevation (Material Design) o rendering personalizzato tramite OutlineProvider.
  • Jetpack Compose supporta le ombre tramite Modifier.shadow() e elevation in Card/Surface.
  • In Flutter, le ombre vengono implementate tramite BoxShadow in BoxDecoration o tramite PhysicalModel.

Cos'è un'ombra nell'interfaccia utente?

L'ombra (shadow) nelle interfacce utente è un effetto visivo che simula illuminazione e profondità. Un'ombra viene creata disegnando una copia scura e sfocata di un elemento, spostata rispetto all'originale. Nel design mobile, le ombre hanno tre funzioni: mostrare la gerarchia (più alto è l'elemento, più lunga è l'ombra), segnalare l'interattività (i pulsanti con ombra sembrano cliccabili), separare il contenuto dallo sfondo (le carte con ombra si leggono come blocchi separati).

Nel Material Design di Google, le ombre sono indissolubilmente legate al concetto di elevation. Ogni elemento occupa una certa altezza nella coordinata z e l'ombra è una proiezione dell'illuminazione dall'alto. Le Human Interface Guidelines di Apple non prescrivono un sistema di altezze rigoroso, ma raccomandano di utilizzare le ombre per creare gerarchia visiva e profondità nelle interfacce.

Secondo uno studio del Nielsen Norman Group (2024), le ombre posizionate correttamente riducono il tempo per trovare un elemento interattivo del 24%. Le ombre aiutano anche gli utenti con disabilità visive a distinguere i confini degli elementi. Tuttavia, l'uso eccessivo di ombre (3+ livelli su uno schermo) aumenta il carico cognitivo e compromette la percezione.

Ombre su iOS: CALayer e SwiftUI

Su iOS, le ombre vengono implementate tramite le proprietà di CALayer: shadowColor imposta il colore dell'ombra, shadowOffset — l'offset in CGSize (x, y), shadowRadius — il raggio di sfocatura, shadowOpacity — l'opacità da 0 a 1. Per le prestazioni, si consiglia anche di impostare shadowPath — un UIBezierPath che descrive la forma dell'ombra, in modo che Core Animation non la calcoli automaticamente.

swift
// UIKit: configurazione dell'ombra tramite CALayer
import UIKit

let cardView = UIView()
cardView.backgroundColor = .systemBackground
cardView.layer.cornerRadius = 12

// Configurazione ombra
cardView.layer.shadowColor = UIColor.black.cgColor
cardView.layer.shadowOpacity = 0.15
cardView.layer.shadowOffset = CGSize(width: 0, height: 4)
cardView.layer.shadowRadius = 8

"> Ottimizzazione: shadowPath esplicito
cardView.layer.shadowPath = UIBezierPath(
    roundedRect: cardView.bounds,
    cornerRadius: 12
).cgPath

In SwiftUI, le ombre vengono impostate tramite il modificatore .shadow() con parametri color, radius, x, y. SwiftUI applica automaticamente shadowPath e gestisce le prestazioni di rendering. Per ombre avanzate (ombre interne, ombre multiple), utilizzare overlay() con LinearGradient.

swift
// SwiftUI: ombra tramite modificatore shadow
import SwiftUI

VStack {
    Text("Carta con ombra")
        .padding()
        .background(Color.white)
        .cornerRadius(16)
        .shadow(
            color: .black.opacity(0.15),
            radius: 10,
            x: 0,
            y: 4
        )
}

Prestazioni delle ombre su iOS: se shadowPath non è impostato, Core Animation calcola l'ombra in base alla forma del layer ogni fotogramma, il che può causare rallentamenti durante l'animazione. Per elementi statici, impostare sempre shadowPath. Per elementi animati, utilizzare shouldRasterize = true su CALayer per memorizzare nella cache l'ombra in un layer separato. In SwiftUI, la rasterizzazione viene gestita tramite .drawingGroup().

Ombre in Android: elevation e ombre personalizzate

In Android, le ombre in Material Design vengono implementate tramite elevation — una proprietà View che imposta l'altezza dell'elemento nella coordinata z. Il sistema renderizza automaticamente un'ombra in base all'elevation e a outlineProvider (contorno dell'elemento). Più alta è l'elevation, più grande e sfocata è l'ombra. Per le View che non ereditano da Material Design (es. AppCompat), le ombre funzionano solo con l'accelerazione hardware abilitata.

xml
<!-- XML: elevation tramite attributi -->
<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">
    <!-- Contenuto della carta -->
</androidx.cardview.widget.CardView>

Per ombre personalizzate nel sistema di View Android, utilizzare OutlineProvider. ViewOutlineProvider consente di impostare una forma arbitraria tramite outline.setRoundRect() o outline.setPath(). Senza OutlineProvider, l'ombra viene renderizzata in base alla forma rettangolare della View, senza considerare l'arrotondamento degli angoli.

kotlin
// Kotlin: OutlineProvider personalizzato per ombra
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  // raggio angolo
        )
    }
}
myView.elevation = 8f  // altezza ombra in px
myView.translationZ = 4f  // altezza aggiuntiva alla pressione

In Jetpack Compose, le ombre vengono gestite tramite Modifier.shadow() e elevation nei componenti Card e Surface. Modifier.shadow() accetta elevation e shape. A differenza del sistema di View, Compose renderizza le ombre nella propria pipeline di rendering, offrendo un comportamento più prevedibile su tutte le versioni di Android.

kotlin
// Jetpack Compose: ombra tramite 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 con ombra personalizzata")
}

Raccomandazioni per l'uso delle ombre

L'uso corretto delle ombre migliora la percezione dell'interfaccia, mentre l'uso improprio crea rumore visivo e degrada le prestazioni. Material Design definisce cinque livelli di elevation: 0 dp (nessuna ombra), 1–4 dp (pulsanti, chip), 6–12 dp (carte, menu), 16–24 dp (dialoghi, fogli inferiori), 24+ dp (finestre modali, FAB).

LivelloElevationElementiCaratteristica dell'ombra
00 dpTesto, icone, sfondoNessuna ombra, strato piatto
11–4 dpPulsanti, campi di inputOmbra leggera, piccolo offset
26–12 dpCarte, SnackbarOmbra moderata, sfocatura notevole
316–24 dpDialoghi, menuOmbra profonda, grande sfocatura
424+ dpFinestre modaliOmbra massima, elemento fluttua

Buone pratiche: (1) non utilizzare ombre per elementi che non devono attirare l'attenzione (sfondi, divisori), (2) l'altezza dell'ombra deve corrispondere all'importanza dell'elemento — più importante, più alta, (3) quando si preme un pulsante, ridurre l'elevation con animazione per il feedback tattile, (4) evitare ombre su sfondi scuri — utilizzare overlay (schiarire/scurire) invece dell'ombra. In Material You (Android 12+), le ombre si adattano al tema: su sfondi scuri, l'elevation viene sostituita da uno strato di colore (surface tint).

Ombre in Flutter: BoxShadow e PhysicalModel

In Flutter, le ombre vengono implementate tramite BoxShadow all'interno di BoxDecoration. BoxShadow accetta color, offset, blurRadius e spreadRadius. Flutter supporta più ombre: passare un elenco di BoxShadow consente di creare effetti complessi — ad esempio, un'ombra esterna + un'evidenziazione interna. PhysicalModel è un'alternativa che simula un'ombra fisica basata sulla forma (BeveledRectangleBorder, CircleBorder).

dart
// Flutter: ombra tramite 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('Contenitore con ombra'),
)

// PhysicalModel per ombra fisica
PhysicalModel(
    color: Colors.white,
    elevation: 6.0,
    shadowColor: Colors.black.withOpacity(0.2),
    borderRadius: BorderRadius.circular(12.0),
    child: Text('Modello fisico con ombra'),
)

Prestazioni: BoxShadow con più ombre può causare un nuovo rendering a ogni modifica. Per elementi statici, utilizzare PhysicalModel — memorizza nella cache l'ombra in un layer separato (RepaintBoundary). In Flutter 3.16+, è supportata elevation di Material 3 con ombre adattive, che cambiano colore e sfocatura in base al tema (chiaro/scuro).

Domande frequenti

In che modo elevation si differenzia da shadow?

Elevation è l'altezza numerica di un elemento nella coordinata z, mentre shadow (ombra) è l'effetto visivo creato da questa altezza. In Android, elevation è una proprietà View che genera automaticamente un'ombra. Nel design, elevation e shadow sono spesso usati in modo intercambiabile, ma tecnicamente elevation è la causa e shadow è l'effetto.

Perché l'ombra non viene visualizzata su Android?

Verificare: (1) la View ha elevation maggiore di 0, (2) outlineProvider è impostato (se la View non proviene dalla libreria Material), (3) l'accelerazione hardware è abilitata (android:hardwareAccelerated="true" nel manifest). CardView gestisce automaticamente outlineProvider. Per View personalizzate, chiamare view.outlineProvider = ViewOutlineProvider.BACKGROUND.

Come creare un'ombra interna (inner shadow) su iOS?

CALayer non supporta le ombre interne direttamente. Utilizzare una combinazione: CAGradientLayer con colore nero e trasparenza all'interno di una maschera, o sovrapporre una UIImageView con un'ombra interna rasterizzata. In SwiftUI, l'ombra interna viene implementata tramite overlay con LinearGradient e blendMode(.multiply).

Come animare l'ombra quando si preme un pulsante?

In Android, utilizzare translationZ per l'animazione: a riposo elevation = 2 dp, alla pressione translationZ = 4 dp (altezza totale 6 dp). Su iOS, animare shadowOffset e shadowRadius contemporaneamente alla trasformazione del pulsante. In SwiftUI, utilizzare withAnimation con il cambiamento di elevation tramite Modifier.shadow(). In Flutter, utilizzare AnimatedContainer con il cambiamento di BoxShadow.

Quante ombre si possono usare su uno schermo?

Material Design raccomanda non più di tre livelli di elevation su uno schermo. Se ci sono più elementi con ombra, raggrupparli con la stessa altezza. Un numero eccessivo di ombre crea rumore visivo e riduce le prestazioni di rendering sui dispositivi di fascia media e bassa.

Riepilogo

  • Shadow (ombra) è un effetto visivo di profondità basato sull'elevation dell'elemento nella coordinata z.
  • Su iOS, le ombre vengono configurate tramite CALayer: shadowColor, shadowOffset, shadowRadius, shadowOpacity.
  • In Android, le ombre vengono gestite tramite elevation e OutlineProvider per la forma personalizzata.
  • Material Design definisce cinque livelli di elevation: 0, 1–4, 6–12, 16–24 e 24+ dp.
  • Jetpack Compose utilizza Modifier.shadow() e elevation in Card/Surface per la gestione delle ombre.
  • Flutter implementa le ombre tramite BoxShadow (BoxDecoration) e PhysicalModel (ombra fisica).
  • Ottimizzazione: impostare shadowPath (iOS), outlineProvider (Android), utilizzare PhysicalModel (Flutter).

Svilupperemo un'applicazione mobile chiavi in mano

IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.

Discuti il progetto

Leggi anche