본문 바로가기
flutter/Widget of the Week

Flutter[플러터] / PhysicalModel을 사용하여 간단하게 위젯에 그림자 추가하기 (쉐도우, BoxShadow, elevation, 고도, 계층, 입체감) PhysicalModel (Flutter Widget of the Week)

by ch5c 2025. 7. 21.
반응형

PhysicalModel class

자식 요소를 모양에 고정하는 물리적 계층을 나타내는 위젯입니다.

https://youtu.be/XgUOSS30OQk

예제 코드

 


우리가 UI를 구성할 때 위젯에 입체감을 줘야 하는 일이 생길 수 있다. 이런 입체감을 가장 쉽게 주는 방법 중 하나는 그림자(DropShadow)를 주는 것인데 이러한 그림자를 쉽게 줄 수 있도록 도와주는 위젯이 바로 PhysicalModel 위젯이다.

PhysicalModel 위젯은 시각적으로 그림자와 입체감을 주기 위해 사용하는 위젯이다. Material 위젯처럼 elevation, shape, color 그리고 clip behavior 등의 속성을 활용할 수 있지만, PhysicalModel은 더 낮은 수준에서 물리적인 형태를 직접 제어할 수 있는 것이 특징이다. 간단하게 알아보자.

하위 속성
속성명 타입 기본값 설명
shape BoxShape BoxShape.rectangle 위젯의 물리적 형태를 지정 (사각형 또는 원형)
clipBehavior Clip Clip.none 자식 위젯을 어떤 방식으로 클리핑할지 결정
borderRadius BorderRadius? null 사각형일 때 적용되는 모서리 반경, 원형일 경우 무시됨
elevation double 0.0 그림자의 높이를 나타내며 클수록 더 진하고 넓게 그림자 표시
color Color 위젯의 배경색 (필수)
shadowColor Color Color(0xFF000000) 그림자의 색상을 지정
child Widget? null 내부에 표시할 자식 위젯

 

사용하는 방법은 정말 간단한데 그림자를 주고 싶은 위젯을 PhysicalModel로 감싸주면 된다.

PhysicalModel(
  child: BlueBox(),
)

그리고 필수 파라미터인 color에 색을 지정해 주면 된다. 일단 Colors.black으로 지정해 주겠다.

PhysicalModel(
  color: Colors.black,
  child: BlueBox(),
)

이렇게 하고 실행해 보자.

코드는 문제없이 잘 실행됐는데도 불가하고 그림자는 눈 씻고 찾아봐도 보이지 않는다. 왜 그런 것일까? 바로 elevation파라미터를 통해 그림자의 크기를 지정해줘야 하기 때문이다. 지정해 주자.

PhysicalModel(
  elevation: 5,
  color: Colors.black,
  child: BlueBox(),
)

위처럼 자연스럽게 그림자가 먹여져 있는 것을 볼 수 있는데 이 그림자의 크기는 elevation 파라미터의 값에 의해 결정된 것이다.

elevation 파라미터는 위젯이 화면의 표면으로부터 얼마나 높이 떠 있는지를 나타내는 값으로, 예를 들어 위에 광원이 있다고 해보자.

그냥 태양이라고 생각하면 된다. 가까이 갈수록 당연하게도 그림자 또한 커질 것이다. 하늘을 나는 비행기의 그림자를 본 적이 있는가? 지상으로부터 너무나 멀리 떨어져서 날기 때문에 그림자조차 엄청나게 거대해져 거의 희미할 정도인데 그 원리와 똑같다. elevation값을 조절하여 그림자의 크기를 키워 줄 수 있지만 그에 따라 그림자는 필연적으로 연해지고 흩어지는 성질을 갖게 된다.

암튼 값을 높이면 아래와 같이 나타난다.

PhysicalModel(
  elevation: 50,
  color: Colors.black,
  child: BlueBox(),
)

근데 여기서 의문이 하나 들 수 있다. color를 다른 다른 색으로 교체해도 그림자의 색이 달라지지 않는다는 것인데 그림자의 색을 조정해 줄려면 color파라미터가 아닌 shadowColor파라미터를 사용해줘야 한다.

PhysicalModel(
  elevation: 5,
  color: Colors.black,
  shadowColor: Colors.red,
  child: BlueBox(),
)

그렇다면 이 color 파라미터는 대체 뭐 하는 놈일까? 그건 현재 코드에서 사용하고 있는 BlueBox위젯의 투명도를 낮춰보면 알 수 있게 된다.

Widget BlueBox() {
  return Container(
    width: 100,
    height: 100,
    color: Colors.blue.withAlpha(0),
  );
}

실행해 보면 웬 검은색 상자가 나타나는데 눈치 빠른 사람들은 이미 눈치를 챗을 것이다. 사실 PhysicalModel이 그림자를 추가하는 방식은 그 위젯 자체에 그림자를 먹이는 것이 아니라 그 위젯 사이즈를 갖고 있는 다른 레이어를 하나 더 만들고 위젯 뒤에 숨겨서 우리가 그림자를 먹이고 싶은 위젯에 그림자가 먹여진 것처럼 보이게 한다는 것이다.

그래서 color 파라미터의 색상을 바꾸면 이 아래에 있는 레이어의 색상이 바뀌게 된다.

PhysicalModel(
  elevation: 5,
  color: Colors.red,
  shadowColor: Colors.black,
  child: BlueBox(),
)

또한 PhysicalModel의 파라미터에서 이 레이어의 모양도 설정해 줄 수 있다. shape를 사용하여 둥글게 만들어주면 아래와 같이 나타난다. (BlueBox의 불투명도를 올렸다.)

PhysicalModel(
  shape: BoxShape.circle,
  elevation: 5,
  color: Colors.black,
  shadowColor: Colors.black,
  child: BlueBox(),
)

이게 왜 필요하냐 할 수도 있는데 되게 중요한 속성이다. 현재 사용하고 있는 BlueBox위젯에 래디우스가 먹여져 있다고 해보자.

Widget BlueBox() {
  return Container(
    width: 100,
    height: 100,
    decoration: BoxDecoration(
      color: Colors.blue,
      borderRadius: BorderRadius.all(Radius.circular(12))
    ),
  );

이거 실행하면 아래와 같이 나타난다.

지금 보면 뒤에 위치해 있는 PhysicalModel의 레이어가 래디우스의 빈틈 사이로 보이는 참사가 일어나고 있다. 이럴 때에 PhysicalModel에 똑같이 래디우스를 먹여준다면 이런 것을 방지할 수 있게 된다.

PhysicalModel(
  borderRadius: BorderRadius.all(Radius.circular(12)),
  elevation: 5,
  color: Colors.black,
  shadowColor: Colors.black,
  child: BlueBox(),
)

자식 위젯과 똑같이 래디우스를 먹여줌으로써 참사를 방지했다.

근데 지금과 같이 Container위젯은 그렇다 쳐도 Flutter에는 수많은 UI 위젯들이 있다. 걔네를 이 위젯으로 감싸면 우리가 원하는 모습은 나올 확률이 적다는 것은 알아두면 좋겠다.

PhysicalModel(
  borderRadius: BorderRadius.all(Radius.circular(12)),
  elevation: 5,
  color: Colors.transparent,
  shadowColor: Colors.black,
  child: FlutterLogo(size: 300,),
)

color파라미터를 Colors.transparent로 변경했음에도 불구하고 그냥 사이즈 전체에 그림자가 먹여져 있기 때문에 썩 마음에 들지 않게 나올 것이다.

 

이렇게 PhysicalModel에 대해서 알아보았다. 이 위젯은 정말 간단한 상황에서 그림자를 먹이기에는 좋다만 위젯에 딱 맞게 그림자를 먹이고 싶다거나 할 때에는 별로 추천하지 않는 위젯이 되겠다. 도움이 되었길 바라며 마치겠다.

반응형