Shadow: 본질, 모바일 애플리케이션에서 그림자 만들기

저자: IT Sectr 게시일: 2026-02-28 읽는 시간: 9 분

Shadow(그림자)는 모바일 인터페이스에서 요소 간의 깊이와 계층 구조의 환상을 만드는 시각적 효과입니다. 그림자는 어떤 요소가 z-평면에서 더 높은 위치에 있어 더 중요하거나 상호작용적임을 보여줍니다. Material Design Guidelines(Google, 2026)에 따르면 시스템 그림자는 5가지 주요 높이(elevation)를 사용합니다: 0 dp(평면), 1–4 dp(버튼), 6–12 dp(카드), 16–24 dp(대화상자), 24+ dp(모달 창). 요소 높이에 대한 자세한 내용은 Elevation 문서를 참조하세요.

핵심 사항

  • 그림자(shadow) — 가장자리 블러로 요소 아래 영역을 어둡게 하여 깊이를 만드는 시각적 효과입니다.
  • iOS에서는 CALayer 속성(shadowColor, shadowOffset, shadowRadius, shadowOpacity)을 통해 그림자를 설정합니다.
  • Android에서는 elevation(Material Design) 또는 OutlineProvider를 통한 사용자 정의 렌더링으로 그림자를 관리합니다.
  • Jetpack Compose는 Modifier.shadow() 및 Card/Surface의 elevation을 통해 그림자를 지원합니다.
  • Flutter에서는 BoxDecoration의 BoxShadow 또는 PhysicalModel을 통해 그림자를 구현합니다.

UI에서 그림자란?

그림자(shadow)는 사용자 인터페이스에서 조명과 깊이를 시뮬레이션하는 시각적 효과입니다. 그림자는 요소의 어둡고 흐릿한 복사본을 원본에서 오프셋하여 그려서 만들어집니다. 모바일 디자인에서 그림자는 세 가지 기능을 합니다: 계층 구조 표시(높은 요소 = 긴 그림자), 상호작용성 신호(그림자가 있는 버튼은 누를 수 있어 보임), 콘텐츠를 배경에서 분리(그림자가 있는 카드는 별도 블록으로 읽힘).

Google의 Material Design에서 그림자는 elevation 개념과 불가분의 관계에 있습니다. 각 요소는 z-좌표에서 특정 높이를 차지하며, 그림자는 위에서 비추는 조명의 투영입니다. Apple의 Human Interface Guidelines는 엄격한 높이 시스템을 규정하지 않지만, 인터페이스에서 시각적 계층 구조와 깊이를 만들기 위해 그림자를 사용할 것을 권장합니다.

Nielsen Norman Group의 연구(2024)에 따르면, 올바르게 배치된 그림자는 상호작용 요소를 찾는 시간을 24% 단축합니다. 그림자는 시각 장애가 있는 사용자가 요소 경계를 구분하는 데도 도움이 됩니다. 그러나 그림자의 과도한 사용(화면에 3+ 레벨)은 인지 부하를 증가시키고 인식을 저하시킵니다.

iOS의 그림자: CALayer와 SwiftUI

iOS에서는 CALayer 속성을 통해 그림자가 구현됩니다: shadowColor는 그림자 색상을 설정하고, shadowOffset — CGSize의 오프셋(x, y), shadowRadius — 블러 반경, shadowOpacity — 0~1의 불투명도입니다. 성능을 위해 shadowPath(그림자 모양을 설명하는 UIBezierPath)를 설정하여 Core Animation이 자동으로 계산하지 않도록 하는 것이 좋습니다.

swift
// UIKit: CALayer를 통한 그림자 구성
import UIKit

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

// 그림자 구성
cardView.layer.shadowColor = UIColor.black.cgColor
cardView.layer.shadowOpacity = 0.15
cardView.layer.shadowOffset = CGSize(width: 0, height: 4)
cardView.layer.shadowRadius = 8

"> 최적화: 명시적 shadowPath
cardView.layer.shadowPath = UIBezierPath(
    roundedRect: cardView.bounds,
    cornerRadius: 12
).cgPath

SwiftUI에서는 .shadow() 수정자로 color, radius, x, y 매개변수를 사용하여 그림자를 설정합니다. SwiftUI는 자동으로 shadowPath를 적용하고 렌더링 성능을 관리합니다. 고급 그림자(내부 그림자, 여러 그림자)의 경우 LinearGradient와 함께 overlay()를 사용하세요.

swift
// SwiftUI: shadow 수정자를 통한 그림자
import SwiftUI

VStack {
    Text("그림자가 있는 카드")
        .padding()
        .background(Color.white)
        .cornerRadius(16)
        .shadow(
            color: .black.opacity(0.15),
            radius: 10,
            x: 0,
            y: 4
        )
}

iOS에서 그림자 성능: shadowPath가 설정되지 않은 경우 Core Animation은 매 프레임마다 레이어 모양을 기반으로 그림자를 계산하므로 애니메이션 중 지연이 발생할 수 있습니다. 정적 요소의 경우 항상 shadowPath를 설정하세요. 애니메이션 요소의 경우 CALayer에서 shouldRasterize = true를 사용하여 그림자를 별도 레이어에 캐시합니다. SwiftUI에서는 래스터화가 .drawingGroup()을 통해 관리됩니다.

Android의 그림자: elevation 및 사용자 정의 그림자

Android에서는 Material Design의 그림자가 elevation(z-좌표에서 요소의 높이를 설정하는 View 속성)을 통해 구현됩니다. 시스템은 elevation과 outlineProvider(요소 윤곽선)를 기반으로 자동으로 그림자를 렌더링합니다. elevation이 높을수록 그림자가 더 크고 흐릿해집니다. Material Design을 상속하지 않는 View(예: AppCompat)의 경우 하드웨어 가속이 활성화된 경우에만 그림자가 작동합니다.

xml
<!-- XML: 속성을 통한 elevation -->
<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">
    <!-- 카드 내용 -->
</androidx.cardview.widget.CardView>

Android View System에서 사용자 정의 그림자를 위해 OutlineProvider를 사용합니다. ViewOutlineProvider를 사용하면 outline.setRoundRect() 또는 outline.setPath()를 통해 임의의 모양을 설정할 수 있습니다. OutlineProvider가 없으면 그림자는 View의 직사각형 모양을 기반으로 렌더링되어 모서리 둥글림이 고려되지 않습니다.

kotlin
// Kotlin: 그림자용 사용자 정의 OutlineProvider
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  // 모서리 반경
        )
    }
}
myView.elevation = 8f  // px 단위 그림자 높이
myView.translationZ = 4f  // 누를 때 추가 높이

Jetpack Compose에서는 Modifier.shadow()와 Card 및 Surface 구성 요소의 elevation을 통해 그림자가 관리됩니다. Modifier.shadow()는 elevation과 shape를 받습니다. View System과 달리 Compose는 자체 렌더 파이프라인에서 그림자를 렌더링하여 모든 Android 버전에서 더 예측 가능한 동작을 제공합니다.

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

그림자 사용 권장 사항

올바른 그림자 사용은 인터페이스 인식을 개선하는 반면, 잘못된 사용은 시각적 노이즈를 만들고 성능을 저하시킵니다. Material Design은 5가지 elevation 레벨을 정의합니다: 0 dp(그림자 없음), 1–4 dp(버튼, 칩), 6–12 dp(카드, 메뉴), 16–24 dp(대화상자, 바텀 시트), 24+ dp(모달 창, FAB).

레벨Elevation요소그림자 특성
00 dp텍스트, 아이콘, 배경그림자 없음, 평면 레이어
11–4 dp버튼, 입력 필드가벼운 그림자, 작은 오프셋
26–12 dp카드, Snackbar중간 그림자, 눈에 띄는 블러
316–24 dp대화상자, 메뉴깊은 그림자, 큰 블러
424+ dp모달 창최대 그림자, 요소가 떠 있음

모범 사례: (1) 주의를 끌지 말아야 할 요소(배경, 구분선)에는 그림자를 사용하지 마세요, (2) 그림자 높이는 요소의 중요성과 일치해야 합니다 — 더 중요할수록 더 높게, (3) 버튼을 누를 때 촉각적 피드백을 위해 애니메이션으로 elevation을 줄이세요, (4) 어두운 배경에서는 그림자를 피하고 대신 overlay(밝게/어둡게)를 사용하세요. Material You(Android 12+)에서는 그림자가 테마에 적응합니다: 어두운 배경에서 elevation은 색상 레이어(surface tint)로 대체됩니다.

Flutter의 그림자: BoxShadow 및 PhysicalModel

Flutter에서는 BoxDecoration 내부의 BoxShadow를 통해 그림자가 구현됩니다. BoxShadow는 color, offset, blurRadius 및 spreadRadius를 받습니다. Flutter는 여러 그림자를 지원합니다: BoxShadow 목록을 전달하면 복잡한 효과(예: 외부 그림자 + 내부 하이라이트)를 만들 수 있습니다. PhysicalModel은 모양(BeveledRectangleBorder, CircleBorder)을 기반으로 물리적 그림자를 시뮬레이션하는 대안입니다.

dart
// Flutter: 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('그림자가 있는 컨테이너'),
)

// 물리적 그림자용 PhysicalModel
PhysicalModel(
    color: Colors.white,
    elevation: 6.0,
    shadowColor: Colors.black.withOpacity(0.2),
    borderRadius: BorderRadius.circular(12.0),
    child: Text('그림자가 있는 물리적 모델'),
)

성능: 여러 그림자가 있는 BoxShadow는 변경 시마다 다시 칠해질 수 있습니다. 정적 요소의 경우 PhysicalModel을 사용하세요 — 별도 레이어(RepaintBoundary)에 그림자를 캐시합니다. Flutter 3.16+에서는 테마(라이트/다크)에 따라 색상과 블러를 변경하는 적응형 그림자가 있는 Material 3 elevation이 지원됩니다.

자주 묻는 질문

elevation과 shadow의 차이점은 무엇인가요?

Elevation은 z-좌표에서 요소의 숫자 높이이고, shadow(그림자)는 이 높이로 생성된 시각적 효과입니다. Android에서 elevation은 자동으로 그림자를 생성하는 View 속성입니다. 디자인에서 elevation과 shadow는 종종 혼용되지만, 기술적으로 elevation은 원인이고 shadow는 결과입니다.

Android에서 그림자가 표시되지 않는 이유는 무엇인가요?

확인 사항: (1) View에 elevation이 0보다 크게 설정되었는지, (2) outlineProvider가 설정되었는지(View가 Material 라이브러리 출처가 아닌 경우), (3) 하드웨어 가속이 활성화되었는지(manifest에서 android:hardwareAccelerated="true"). CardView는 자동으로 outlineProvider를 관리합니다. 사용자 정의 View의 경우 view.outlineProvider = ViewOutlineProvider.BACKGROUND를 호출하세요.

iOS에서 내부 그림자(inner shadow)를 만드는 방법은?

CALayer는 내부 그림자를 직접 지원하지 않습니다. 마스크 내부에 검은색과 투명도를 가진 CAGradientLayer를 사용하거나, 래스터 내부 그림자가 있는 UIImageView를 오버레이하세요. SwiftUI에서는 내부 그림자가 LinearGradient와 blendMode(.multiply)를 사용한 overlay로 구현됩니다.

버튼을 누를 때 그림자를 애니메이션화하는 방법은?

Android에서는 translationZ를 사용하여 애니메이션합니다: 기본 상태 elevation = 2 dp, 누를 때 translationZ = 4 dp(총 높이 6 dp). iOS에서는 버튼 변환과 동시에 shadowOffset과 shadowRadius를 애니메이션합니다. SwiftUI에서는 Modifier.shadow()를 통한 elevation 변경과 함께 withAnimation을 사용합니다. Flutter에서는 BoxShadow 변경과 함께 AnimatedContainer를 사용합니다.

하나의 화면에 얼마나 많은 그림자를 사용할 수 있나요?

Material Design은 하나의 화면에 세 개 이하의 elevation 레벨을 권장합니다. 그림자가 있는 요소가 더 많으면 동일한 높이로 그룹화하세요. 과도한 그림자는 시각적 노이즈를 만들고 중급 및 보급형 기기에서 렌더링 성능을 저하시킵니다.

요약

  • 그림자(shadow)는 z-좌표에서 요소 elevation에 기반한 시각적 깊이 효과입니다.
  • iOS에서는 CALayer(shadowColor, shadowOffset, shadowRadius, shadowOpacity)로 그림자를 구성합니다.
  • Android에서는 elevation과 사용자 정의 모양을 위한 OutlineProvider로 그림자를 관리합니다.
  • Material Design은 5가지 elevation 레벨을 정의합니다: 0, 1–4, 6–12, 16–24, 24+ dp.
  • Jetpack Compose는 그림자 관리를 위해 Card/Surface에서 Modifier.shadow()와 elevation을 사용합니다.
  • Flutter는 BoxShadow(BoxDecoration)와 PhysicalModel(물리적 그림자)로 그림자를 구현합니다.
  • 최적화: shadowPath 설정(iOS), outlineProvider(Android), PhysicalModel 사용(Flutter).

턴키 방식의 모바일 애플리케이션을 개발해 드립니다

IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.

프로젝트 논의

더 읽어보기