티스토리 뷰

홈 화면 레이아웃 그리기

 

그릴 화면

 

일단 그려야 할 홈 화면의 레이아웃이다.

 

라우팅은 버튼을 통해서 이루어지며, 

도전과제 클리어 여부, 운동기록의 여부에 따라 다른 화면을 출력할 것이다.

 

단색으로 이루어진 아이콘이 아닌 경우 

라이트모드와 다크모드에서 출력할 svg파일 2개를 프로젝트에 추가해줘야 한다.

 

 

common_outline_button.dart

import 'package:escape_anchovy/res/text/colors.dart';
import 'package:flutter/material.dart';

class CommonOutlineButton extends StatefulWidget {
  const CommonOutlineButton(
      {super.key,
      this.width = double.maxFinite,
      this.height = 50,
      this.onPressed,
      this.text = '',
      this.textStyle,
      this.textColor,
      this.borderColor,
      this.borderRadius = 10});

  final double width;
  final double height;
  final void Function()? onPressed;
  final String text;
  final TextStyle? textStyle;
  final Color? textColor;
  final Color? borderColor;
  final double borderRadius;

  @override
  State<CommonOutlineButton> createState() => _CommonOutlineButtonState();
}

class _CommonOutlineButtonState extends State<CommonOutlineButton> {
  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: widget.width,
      height: widget.height,
      child: OutlinedButton(
          onPressed: widget.onPressed,
          style: OutlinedButton.styleFrom(
            side: BorderSide(
                width: 1.0,
                color: widget.borderColor ??
                    (context.isLight
                        ? LightModeColors.blue
                        : DarkModeColors.blue)),
            padding: const EdgeInsets.all(0.0),
            foregroundColor: widget.textColor ??
                (context.isLight ? LightModeColors.blue : DarkModeColors.blue),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(widget.borderRadius),
            ),
          ),
          child: Text(widget.text, style: widget.textStyle)),
    );
  }
}

만들어둔 버튼 위젯이 있지만 홈 화면에서 사용되는 버튼들은

테두리만 있는 버튼이라 OutlinedButton을 반환하는

버튼 클래스를 하나 더 만들면 좋겠다는 생각이 들었다.

 

textColor와 borderColor 프로퍼티로 버튼의 색상을 컨트롤 할 수 있다.

 

 

home_screen.dart

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key, required this.settingController});

  static const routeName = '/home';

  final SettingController settingController;

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final _controller = HomeController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CommonAppBar(
        title: '메인화면',
        isHome: true,
        settingController: widget.settingController,
      ),
      body: AnimatedBuilder(
          animation: _controller,
          builder: (context, snapshot) {
            return _buildPage(context);
          }),
    );
  }

  Widget _buildPage(BuildContext context) {
    return Column(
      children: [
        const SizedBox(
          height: 24,
        ),
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 30.0),
          child: Column(
            children: [
              Row(
                children: [
                  Row(
                    children: [
                      SvgPicture.asset(context.isLight
                          ? 'asset/svg/heart.svg'
                          : 'asset/svg/dark_heart.svg'),
                      const SizedBox(
                        width: 8,
                      ),
                      Text(
                        '운동하기',
                        style: TextStyles.b1Medium.copyWith(
                            color: context.isLight
                                ? DarkModeColors.background
                                : LightModeColors.background),
                      ),
                      const SizedBox(
                        width: 4,
                      ),
                      GestureDetector(
                        onTap: () {},
                        child: Text(
                          '(자세한 설명 보기)',
                          style: TextStyles.b3Regular.copyWith(
                              color: context.isLight
                                  ? LightModeColors.dark2
                                  : DarkModeColors.dark2),
                        ),
                      )
                    ],
                  ),
                ],
              ),
              const SizedBox(
                height: 20,
              ),
              Center(
                child: GestureDetector(
                  onTap: () {},
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: [
                      SvgPicture.asset(context.isLight
                          ? 'asset/svg/exercise_category.svg'
                          : 'asset/svg/dark_exercise_category.svg'),
                      const SizedBox(
                        height: 4,
                      ),
                      Text(
                        '운동항목',
                        style: TextStyles.b1Regular.copyWith(
                            color: context.isLight
                                ? DarkModeColors.background
                                : LightModeColors.background),
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(
                height: 16,
              ),
              CommonOutlineButton(
                width: 180,
                height: 40,
                onPressed: () {},
                text: '운동시작',
                textStyle: TextStyles.b1Medium,
                borderRadius: 8,
              ),
            ],
          ),
        ),
        divideSection(),
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 30),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                children: [
                  Expanded(
                    child: Row(
                      children: [
                        SvgPicture.asset(context.isLight
                            ? 'asset/svg/note.svg'
                            : 'asset/svg/dark_note.svg'),
                        const SizedBox(
                          width: 9,
                        ),
                        Text(
                          '최근 일지 확인',
                          style: TextStyles.b1Medium.copyWith(
                              color: context.isLight
                                  ? DarkModeColors.background
                                  : LightModeColors.background),
                        ),
                        const SizedBox(
                          width: 4,
                        ),
                        Text(
                          '(3일)',
                          style: TextStyles.b3Regular.copyWith(
                              color: context.isLight
                                  ? LightModeColors.dark2
                                  : DarkModeColors.dark2),
                        )
                      ],
                    ),
                  ),
                  CommonOutlineButton(
                    width: 65,
                    height: 18,
                    onPressed: () {},
                    text: '전체 일지 확인',
                    textStyle: TextStyles.caption2.copyWith(height: 0.01),
                  )
                ],
              ),
              const SizedBox(
                height: 7,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(
                    '당신의 성장과정을 살펴보세요.',
                    style: TextStyles.b3Regular.copyWith(
                        color: context.isLight
                            ? LightModeColors.dark2
                            : DarkModeColors.dark2),
                  ),
                ],
              ),
              Center(
                child: Column(
                  children: [
                    const SizedBox(
                      height: 18,
                    ),
                    SvgPicture.asset(
                      'asset/svg/no_data.svg',
                      colorFilter: ColorFilter.mode(
                          context.isLight
                              ? LightModeColors.dark3
                              : DarkModeColors.dark3,
                          BlendMode.srcIn),
                    ),
                    const SizedBox(
                      height: 8,
                    ),
                    Text(
                      '운동기록이 없습니다',
                      style: TextStyles.b2Medium.copyWith(
                          color: context.isLight
                              ? LightModeColors.dark3
                              : DarkModeColors.dark3),
                    ),
                    Text(
                      '운동시작을 눌러 일지를 작성해보세요',
                      style: TextStyles.b4Regular.copyWith(
                          color: context.isLight
                              ? const Color(0XFFADA8B0)
                              : const Color(0XFF8A848D)),
                    ),
                  ],
                ),
              )
            ],
          ),
        ),
        divideSection(),
        Padding(
          padding: const EdgeInsets.symmetric(horizontal: 30),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                children: [
                  Expanded(
                    child: Row(
                      children: [
                        SvgPicture.asset(context.isLight
                            ? 'asset/svg/challenges.svg'
                            : 'asset/svg/dark_challenges.svg'),
                        const SizedBox(
                          width: 7,
                        ),
                        Text(
                          '도전과제',
                          style: TextStyles.b1Medium.copyWith(
                              color: context.isLight
                                  ? DarkModeColors.background
                                  : LightModeColors.background),
                        ),
                        const SizedBox(
                          width: 8,
                        ),
                        CommonOutlineButton(
                          width: 65,
                          height: 18,
                          onPressed: () {},
                          text: '전체 업적 확인',
                          textStyle: TextStyles.caption2.copyWith(height: 0.01),
                          textColor: context.isLight
                              ? LightModeColors.yellow
                              : DarkModeColors.yellow,
                          borderColor: context.isLight
                              ? LightModeColors.yellow
                              : DarkModeColors.yellow,
                        )
                      ],
                    ),
                  ),
                ],
              ),
              const SizedBox(
                height: 7,
              ),
              Text(
                '운동을 완료하고 도전과제를 달성해보세요.',
                style: TextStyles.b3Regular.copyWith(
                    color: context.isLight
                        ? LightModeColors.dark2
                        : DarkModeColors.dark2),
              ),
              const SizedBox(
                height: 3,
              ),
              Text.rich(TextSpan(children: [
                TextSpan(
                  text: '고등어 ',
                  style: TextStyles.b3Regular.copyWith(
                      color: context.isLight
                          ? LightModeColors.blue
                          : DarkModeColors.blue),
                ),
                TextSpan(
                  text: '도전과제를 달성하면 무언가 바뀔수도?',
                  style: TextStyles.b3Regular.copyWith(
                      color: context.isLight
                          ? LightModeColors.dark2
                          : DarkModeColors.dark2),
                )
              ])),
            ],
          ),
        ),
      ],
    );
  }

  Widget divideSection() {
    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.symmetric(vertical: 20.0),
          child: Container(
            height: 8,
            color:
                context.isLight ? LightModeColors.dark4 : DarkModeColors.dark4,
          ),
        ),
      ],
    );
  }
}

context.isLight를 활용해 라이트모드와 다크모드일 때의 색상을 지정해준다.

 

구역을 나누는 컨테이너를 위젯을 따로 분류하여 코드 가독성을 높였다.

 

 

 

운동 설명 다이얼로그 추가

 

 

 

화면 폴더에 다이얼로그 폴더를 추가해

그 안에 '다이얼로그이름.dart' 파일을 추가한다.

 

 

explain_dialog.dart

class ExplainDialog {
  static void showBottomSheet(BuildContext context) {
    showModalBottomSheet(
        context: context,
        builder: (context) => Container(
              height: 300,
              decoration: BoxDecoration(
                color: context.isLight
                    ? LightModeColors.background
                    : DarkModeColors.dark4,
                borderRadius: const BorderRadius.only(
                  topLeft: Radius.circular(20.0),
                  topRight: Radius.circular(20.0),
                ),
              ),
              child: Column(children: [
                const SizedBox(
                  height: 14,
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    const SizedBox(
                      width: 40, // svgSize(24) + svgPadding(12)
                    ),
                    const Text(
                      '운동설명',
                      style: TextStyles.h2Bold,
                    ),
                    Padding(
                      padding: const EdgeInsets.only(right: 16.0),
                      child: GestureDetector(
                          onTap: () {
                            Navigator.pop(context);
                          },
                          child: const Icon(
                            Icons.close,
                          )),
                    )
                  ],
                ),
                const SizedBox(
                  height: 20,
                ),
                const Padding(
                  padding: EdgeInsets.symmetric(horizontal: 24.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        '운동방식',
                        style: TextStyles.b1Medium,
                      ),
                      SizedBox(height: 4),
                      Text(
                        '운동시작을 누르면 2개의 종목을 번갈아가며 6세트를 진행합니다. 한 세마다 자신이 수행할 수 있는 최대의 개수를 수행해주세요. 오늘의 기록을 전날의 기록과 비교해서 볼 수 있습니다. 전날보다 1개씩 더하는 걸 목표로 삼아보세요!',
                        style: TextStyles.b4Regular,
                      ),
                      SizedBox(height: 20),
                      Text(
                        '설정',
                        style: TextStyles.b1Medium,
                      ),
                      SizedBox(height: 4),
                      Text(
                        '운동항목 아이콘을 눌러 2개의 항목을 선택해주세요. 항목당 하나의 운동만 선택할 수 있습니다. 휴식시간은 2분으로 설정되어 있습니다.',
                        style: TextStyles.b4Regular,
                      ),
                    ],
                  ),
                )
              ]),
            ));
  }
}

클래스 내에서 static형의 다이얼로그 생성함수를 작성해 

클래스의 인스턴스를 따로 생성할 필요없이 바로 클래스의 함수를 갖다 쓸 수 있도록 해준다.

 

showModalBottomSheet는 하단 다이얼로그를 생성하는 플러터의 내장함수이다.

 

 

Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,
  children: [
    const SizedBox(
	  width: 40, // svgSize(24) + svgPadding(12)
	),
	const Text(
	  '운동설명',
	  style: TextStyles.h2Bold,
	),
	Padding(
	  padding: const EdgeInsets.only(right: 16.0),
	  child: GestureDetector(
	      onTap: () {
		    Navigator.pop(context);
		  },
		  child: const Icon(
		    Icons.close,
		  )),
	)
  ]
)

저번에 앱바를 구현했던 것처럼 글자와 아이콘을

한 줄에 배치할 때 글자를 중앙으로 주고싶다면

아이콘이 차지하는 width값과 반대쪽 width값을 같게 해주면 된다.

 

 

Text(
	'운동시작을 누르면 2개의 종목을 번갈아가며 6세트를 진행합니다. 한 세마다 자신이 수행할 수 있는 최대의 개수를 수행해주세요. 오늘의 기록을 전날의 기록과 비교해서 볼 수 있습니다. 전날보다 1개씩 더하는 걸 목표로 삼아보세요!',
	style: TextStyles.b4Regular,
),

Text 위젯은 Row나 Column안에 있는 상태에서

화면이 넘어가는 길이를 가지면 overflow가 나게 되지만 

단일 객체로써 쓰게되면 화면을 넘어가는 시점에서 자동으로 개행이된다. 

 

 

 

테스트

 

레이아웃 그리는 게 전부라 이번 파트는 별로 어려운 게 없었다.

 

 

 

깃허브

 

GitHub - hamond12/EscapeAnchovy: 운동일지 앱

운동일지 앱. Contribute to hamond12/EscapeAnchovy development by creating an account on GitHub.

github.com

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함