Flutter Responsive & Adaptive UI: 폴더블, 태블릿, 데스크톱 단일 코드베이스 완전 대응

Flutter의 가장 큰 장점 중 하나는 단일 코드베이스로 여러 플랫폼을 지원한다는 것입니다. 하지만 “돌아간다”는 것과 “각 디바이스에 맞게 훌륭한 사용자 경험을 제공한다”는 것은 전혀 다른 이야기입니다. 스마트폰, 폴더블 기기, 태블릿, 그리고 넓은 데스크톱 모니터까지 다양한 폼팩터에서 완벽하게 작동하는 반응형(Responsive) 및 적응형(Adaptive) UI를 구축하는 방법을 살펴보겠습니다.
반응형(Responsive) vs 적응형(Adaptive)
- 반응형(Responsive): 화면 크기나 방향이 변경될 때 레이아웃이 유동적으로 조정됩니다. (예:
MediaQuery,LayoutBuilder,Flex) - 적응형(Adaptive): 디바이스의 종류(모바일, 데스크톱, 폴더블 등)나 OS(iOS, Android, Windows)에 맞게 UI 컴포넌트나 인터랙션 방식이 완전히 달라집니다. (예: 터치 vs 마우스, 스와이프 vs 단축키)
1. 반응형 레이아웃의 기초: LayoutBuilder
화면 크기에 따라 단순히 위젯을 늘리는 것이 아니라, 화면 공간을 어떻게 활용할지 결정해야 합니다. LayoutBuilder는 부모 위젯의 크기 제약에 따라 UI를 다르게 그릴 수 있게 해줍니다.
class ResponsiveScaffold extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth >= 1024) {
// 데스크톱 레이아웃
return DesktopLayout();
} else if (constraints.maxWidth >= 600) {
// 태블릿 레이아웃
return TabletLayout();
} else {
// 모바일 레이아웃
return MobileLayout();
}
},
);
}
}
이 접근 방식을 사용하면 넓은 화면에서는 사이드바(NavigationRail 또는 Drawer)를 표시하고, 좁은 화면에서는 하단 탭 바(BottomNavigationBar)를 표시하도록 구조를 변경할 수 있습니다.
2. 폴더블 디바이스 대응: 디스플레이 힌지 피하기
폴더블 기기는 화면 가운데 힌지(Hinge)가 존재할 수 있습니다. 화면을 반쯤 접은 “플렉스 모드(Flex Mode)“와 같은 상태를 처리하기 위해 MediaQuery.displayFeatures를 활용해야 합니다.
Widget build(BuildContext context) {
final displayFeatures = MediaQuery.of(context).displayFeatures;
final hinge = displayFeatures.firstWhereOrNull(
(feature) => feature.type == DisplayFeatureType.hinge ||
feature.type == DisplayFeatureType.fold,
);
if (hinge != null) {
// 힌지를 기준으로 화면을 분할하여 렌더링
return FlexLayoutWithHinge(hingeBounds: hinge.bounds);
}
// 일반 디스플레이
return NormalLayout();
}
Google에서 제공하는 dual_screen 패키지나 TwoPane 위젯을 사용하면 리스트-디테일 구조를 폴더블 기기에서 손쉽게 구현할 수 있습니다.
3. 적응형 UI: 플랫폼별 최적화 경험 제공
데스크톱 환경에서는 터치스크린이 아닌 마우스와 키보드를 사용합니다. 따라서 마우스 호버(Hover) 효과와 단축키 지원이 필수적입니다.
마우스 호버 효과
마우스를 올렸을 때의 피드백을 제공하기 위해 FocusableActionDetector나 단순한 MouseRegion을 사용할 수 있습니다.
MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) => setState(() => _isHovering = true),
onExit: (_) => setState(() => _isHovering = false),
child: Container(
color: _isHovering ? Colors.blue.shade100 : Colors.white,
child: Text('호버해보세요'),
),
)
폼팩터에 맞는 스크롤 동작
데스크톱에서는 마우스 휠 스크롤이 자연스러워야 합니다. 최신 Flutter에서는 기본적으로 지원하지만, 커스텀 스크롤 동작이 필요한 경우 ScrollBehavior를 재정의할 수 있습니다.
요약
완벽한 크로스 플랫폼 앱을 만들기 위해서는 단순한 화면 늘리기를 넘어선 고민이 필요합니다.
LayoutBuilder를 통한 중단점(Breakpoint) 기반 레이아웃 분기MediaQuery.displayFeatures를 통한 폴더블 폼팩터 대응- 마우스, 키보드 입력 및 OS별 UI 스타일을 고려한 적응형 컴포넌트 설계
이러한 기법들을 적용하여 모든 디바이스에서 네이티브 앱처럼 느껴지는 훌륭한 Flutter 앱을 개발해보세요!