본문 바로가기
공부용 프로젝트/Clock_App

Clock_App / 공통 UI 제작하기

by ch5c 2025. 10. 24.
반응형

 

각 화면들이 공통적으로 가지고 있는 요소가 무엇이 있을까? 기본적으론 배경색, 내비게이션 바를 꼽을 수 있을 것이다.

근데 여기에서 이제 내비게이션 바는 제작을 완료했으니 배경색을 다 적용해 주면 된다.

허나 화면 하나하나 마다 배경색을 일일이 적용시키는 것은 상당히 비효율 적일 것이다. 그러므로 공통적으로 사용하는 기능이나 레이아웃을 묶어둔 기본 뼈대 클래스를 제작해 줄 것이다.

 

맨 처음 프로젝트 구조화를 했을 때 트리를 아래와 같이 만들어 놨었다.

lib/
 ├─ constant/
 ├─ controllers/
 ├─ screens/
 │   ├─ components/
 │   ├─ widgets/
 └─ main.dart 

 

여기에서 이제 widgets/안에 파일을 만들면 된다.


Flutter에서 화면의 배경 색, backgroundColor를 설정하려면 무엇을 해야 할까? 바로 가장 기본이 되는 위젯 Scaffold를 사용해 주면 될 것이다.

근데 앞서 말했듯이 모든 화면에 Scaffold위젯을 사용하고 파라미터로 붙어 있는 backgroundColor를 일일이 매우 비효율 적이기 때문에 이것을 위젯으로 만들어 줄 것이다.

 

파일 이름은base_scaffold.dart로 했다.

import 'package:clock_app/constant/color.dart';
import 'package:flutter/material.dart';

class BaseScaffold extends StatelessWidget {
  final Widget body;
  const BaseScaffold({super.key, required this.body,});

  @override
  Widget build(BuildContext context) {
    final mediaWidth = MediaQuery.sizeOf(context).width;
    return Scaffold(
      backgroundColor: kBackgroundColor,
      body: Padding(
        padding: const EdgeInsets.symmetric(
          horizontal: 16,
        ),
        child: SizedBox(
          width: mediaWidth,
          child: body,
        ),
      ),
    );
  }
}

 

이제 BaseScaffold()라는 이름의 클래스를 불러올 수 있게 된다. 근데 여기서 끝이 아니다. 위의 사진을 봐보면 공통적인 요소가 하나 더 있는데 바로 앱 바의 햄버거 아이콘 부분이다. 근데 이렇게 말할 수 있다.

"이거 사용되는 화면이 두 개 밖에 없는데 이게 어떻게 공통 요소임? 이거 분류 할꺼면 widgets/가 아니라 components/로 가야 하는거 아님?" 라면서 말이다.

근데 사용하는 곳이 두 곳이나 있으면 사실 공통 요소가 맞기 때문에 그냥 넣었다.

그렇다면 바로 만들어놓은 BaseScaffoldappBar 파라미터를 추가해 주자. 

import 'package:clock_app/constant/color.dart';
import 'package:flutter/material.dart';

class BaseScaffold extends StatelessWidget {
  final Widget body;
  final PreferredSizeWidget? appBar;
  const BaseScaffold({
    super.key,
    required this.body,
    this.appBar,
  });

  @override
  Widget build(BuildContext context) {
    final mediaWidth = MediaQuery.sizeOf(context).width;
    return Scaffold(
      appBar: appBar,
      backgroundColor: kBackgroundColor,
      body: Padding(
        padding: const EdgeInsets.symmetric(
          horizontal: 16,
        ),
        child: SizedBox(
          width: mediaWidth,
          child: body,
        ),
      ),
    );
  }
}

 

만들면서 처음 알았는데 처음에 appBar 인자를 만들면서 자연스럽게 클래스를 AppBar로 했다만 오류가 나서 그제서야 코드에 보이는 것처럼 PreferredSizeWidget으로 바꿔줬다. 그렇다. 놀랍게도 앱바는 앱바 클래스가 아닌 것이다.

꼭 사용되어야 하는 필수 인자가 아니라면 final로 선언한 인스터스 객체의 클래스에?(nullable)를 붙이고 생성자 부분에서는 required를 빼주면 된다. 이렇게 하면 그 클래스를 사용하는 화면에서 선택적으로 주어진 인자값을 사용할 수 있게 된다.

근데 보면 알겠지만 앱 바를 인자값으로 넘겨버렸다. 왜냐하면 이제 이 화면,  BaseScaffold는 이미 제 할일을 끝냈기 떄문에 다른 파일로 역할을 분담해주는 것이다.

그렇게 역할이 분담된 앱바는 base_app_bar.dart라는 이름으로 만들어주었다.

import 'package:clock_app/constant/color.dart';
import 'package:flutter/material.dart';

class BaseAppBar extends StatelessWidget implements PreferredSizeWidget{
  final VoidCallback onTap;
  const BaseAppBar({super.key, required this.onTap});

  @override
  Widget build(BuildContext context) {
    return AppBar(
      toolbarHeight: 88,
      backgroundColor: kBackgroundColor,
      actionsPadding: EdgeInsets.symmetric(
        horizontal: 24,
      ),
      actions: [
        GestureDetector(
          onTap: onTap,
          child: Icon(Icons.more_vert),
        ),
      ],
    );
  }

  @override
  Size get preferredSize => const Size.fromHeight(88);
}

 

크기는 앞서 Figma에서 만들었던 것 처럼 세로 88, 양옆으로 패딩 24를 주었다. 

또 신기한 것이 앱바를 위젯으로 만들려면 아래에 @override되어 있는 것 같이 dpreferredSize를 지정해줘야 한다. (안 그러면 오류 남)

 

fromHeight에 들어가는 AppBar의 기본 객체는 kToolBarHeight으로 사이즈는 56.0이다. (현재 코드에서는 그것을 88로 바꿔놓음)

이제 사용은 아래와 같이 해주면 된다.

@override
Widget build(BuildContext context) {
  return BaseScaffold(
    appBar: BaseAppBar(onTap: () {},),
    body: Column(),
  );
}

 

반응형