PhysicalModel class
자식 요소를 모양에 고정하는 물리적 계층을 나타내는 위젯입니다.
예제 코드
우리가 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에 대해서 알아보았다. 이 위젯은 정말 간단한 상황에서 그림자를 먹이기에는 좋다만 위젯에 딱 맞게 그림자를 먹이고 싶다거나 할 때에는 별로 추천하지 않는 위젯이 되겠다. 도움이 되었길 바라며 마치겠다.