728x90
Flutter 반응형 UI 구현하기: ScreenUtil을 활용한 디바이스 대응
들어가며
Flutter로 앱을 개발하다 보면 다양한 디바이스에 대응해야 하는 상황이 발생합니다. 특히 올리브영과 같은 커머스 앱에서는 상품 이미지, 텍스트 크기, 여백 등이 디바이스 크기에 따라 적절하게 조정되어야 합니다. 이번 포스트에서는 ScreenUtil을 활용한 반응형 UI 구현 과정을 상세히 다뤄보겠습니다.
1. ScreenUtil 구현하기
1.1 기본 설정
먼저 utils 폴더에 screen_util.dart 파일을 생성하고 기본 클래스를 구현합니다.
class ScreenUtil {
static late MediaQueryData _mediaQueryData;
static late double screenWidth;
static late double screenHeight;
static late double defaultSize;
static late Orientation orientation;
// 디자인 기준 크기 설정
static const double designWidth = 375.0; // 디자인 시안 기준 너비
static const double designHeight = 812.0; // 디자인 시안 기준 높이
void init(BuildContext context) {
_mediaQueryData = MediaQuery.of(context);
screenWidth = _mediaQueryData.size.width;
screenHeight = _mediaQueryData.size.height;
orientation = _mediaQueryData.orientation;
defaultSize = screenWidth / 100; // 1% of screen width
}
}
1.2 크기 변환 메서드 구현
화면 크기에 따른 비율 계산을 위한 메서드들을 추가합니다.
static double getProportionateScreenWidth(double inputWidth) {
return (inputWidth / designWidth) * screenWidth;
}
static double getProportionateScreenHeight(double inputHeight) {
return (inputHeight / designHeight) * screenHeight;
}
static double getAdaptiveFontSize(double fontSize) {
double scaleFactor = screenWidth / designWidth;
return fontSize * scaleFactor;
}
1.3 확장 메서드 추가
사용 편의성을 위한 확장 메서드를 구현합니다.
extension SizeExtension on num {
double get w => ScreenUtil.getProportionateScreenWidth(toDouble());
double get h => ScreenUtil.getProportionateScreenHeight(toDouble());
double get sp => ScreenUtil.getAdaptiveFontSize(toDouble());
}
2. 공통 스타일 정의하기
2.1 styles.dart 파일 생성
utils 폴더에 styles.dart 파일을 만들어 공통 스타일을 정의합니다.
class AppStyles {
// 색상
static const Color mainColor = Color(0xFFC9C138);
static const Color greyColor = Colors.grey;
// 텍스트 스타일
static TextStyle get headingStyle => TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: Colors.black,
);
static TextStyle get bodyTextStyle => TextStyle(
fontSize: 14.sp,
color: Colors.black,
);
// 여백
static EdgeInsets get defaultPadding => EdgeInsets.all(16.w);
static EdgeInsets get horizontalPadding => EdgeInsets.symmetric(horizontal: 16.w);
// 카드 관련
static double get productCardWidth => 150.w;
static double get productCardHeight => 150.h;
static BorderRadius get defaultRadius => BorderRadius.circular(8.w);
}
3. 디바이스 정보 모니터링
3.1 DevicePreviewWidget 구현
개발 중 화면 정보를 실시간으로 확인할 수 있는 위젯을 만듭니다.
class DevicePreviewWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8.w),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(8.w),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'📱 디바이스 정보',
style: TextStyle(
color: Colors.white,
fontSize: 14.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 4.h),
Text(
'너비: ${ScreenUtil.screenWidth.toStringAsFixed(1)}',
style: TextStyle(color: Colors.white, fontSize: 12.sp),
),
Text(
'높이: ${ScreenUtil.screenHeight.toStringAsFixed(1)}',
style: TextStyle(color: Colors.white, fontSize: 12.sp),
),
Text(
'방향: ${MediaQuery.of(context).orientation.name}',
style: TextStyle(color: Colors.white, fontSize: 12.sp),
),
],
),
);
}
}
4. 실제 적용 과정
4.1 main.dart에서 초기화
앱 시작 시 ScreenUtil을 초기화합니다.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
builder: (context, child) {
ScreenUtil().init(context);
return MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: child!,
);
},
// ... 기타 설정
);
}
}
4.2 화면 방향 대응
OrientationBuilder를 사용하여 화면 방향에 따른 레이아웃을 조정합니다.
OrientationBuilder(
builder: (context, orientation) {
final isPortrait = orientation == Orientation.portrait;
return Scaffold(
body: Stack(
children: [
Column(
children: [
// 배너 섹션
Container(
height: isPortrait ? 200.h : 150.h,
// ... 배너 내용
),
// 퀵 메뉴 섹션
GridView.count(
crossAxisCount: isPortrait ? 5 : 8,
childAspectRatio: isPortrait ? 1 : 1.2,
// ... 그리드 설정
),
// 상품 섹션
Container(
width: isPortrait ?
AppStyles.productCardWidth :
AppStyles.productCardWidth * 0.8,
// ... 상품 카드 내용
),
],
),
if (kDebugMode)
Positioned(
top: ScreenUtil.safeAreaTop + 60.h,
right: 16.w,
child: DevicePreviewWidget(),
),
],
),
);
},
)
5. 문제 해결 과정
5.1 픽셀 오버플로우 해결
화면 회전 시 발생하는 픽셀 오버플로우 문제를 다음과 같이 해결했습니다:
- Flexible 위젯 사용
Flexible(
child: Text(
title,
style: AppStyles.headingStyle,
),
)
2.AspectRatio 적용
AspectRatio(
aspectRatio: 1,
child: ClipRRect(
borderRadius: AppStyles.defaultRadius,
child: Image.asset(
imageUrl,
fit: BoxFit.cover,
),
),
)
3. SafeArea 적용
SafeArea(
child: Stack(
children: [
// ... 레이아웃 내용
],
),
)
6. 테스트 및 검증
6.1 다양한 디바이스 테스트
- Galaxy S21 Ultra (1440 x 3200)
- 가로/세로 모드 전환
- 다양한 화면 크기 에뮬레이터
6.2 확인 항목
- 텍스트 크기의 적절성
- 이미지 비율 유지
- 여백과 간격의 일관성
- 가로 모드에서의 레이아웃 정상 동작
마무리
ScreenUtil을 활용한 반응형 UI 구현을 통해 다음과 같은 이점을 얻을 수 있었습니다:
- 디바이스 크기에 관계없이 일관된 UI 제공
- 코드의 재사용성과 유지보수성 향상
- 개발 생산성 증가
- 사용자 경험 개선
앞으로도 태블릿 지원이나 성능 최적화 등 추가적인 개선을 진행할 예정입니다.
참고 자료
728x90
'코딩 > ♠♠기술 구현' 카테고리의 다른 글
홈화면 UI 만들기 (0) | 2024.10.30 |
---|---|
히스토리 화면 구현 과정 (0) | 2024.10.30 |
깃으로 협업하기 (0) | 2024.10.28 |
시연영상 촬영 (4) | 2024.10.05 |
인 앱 알림 구현 과정 (2) | 2024.10.05 |