티스토리 뷰

파일 분리

 

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Home'),
    );
  }
}

// MyHomePage 클래스는 생략함

 

main.dart 파일을 보면

MyApp이라는 클래스를 실행시키는 main 함수,

MyApp 클래스, MyHomePage 클래스 이렇게 3개 부분으로 이루어져 있다.

 

우린 이걸 하나씩 다 분리를 할 것이다.

 

나중에 개발하다 보면 코드가 몇백줄이 넘어갈텐데

이걸 미리 분리해둬야 나중에 코드 관리가 편리하다.

 

 

 

위치 잘보고 파일 이런식으로 생성해주자

일단 지금당장 필요한 홈화면과 스플래쉬 화면을 2개 추가해줬다.

 

추가하고 싶은 화면이 있다면 screen폴더에 

화면이름 폴더를 추가하고 그 밑에 

'화면이름_screen.dart', '화면이름_controller.dart' 를 추가해주는 방식이다.

 

controller 파일은 화면에서 사용할 변수나 함수같은 걸 넣어두는 클래스이고

screen 파일은 말 그대로 해당화면을 보여주는 클래스이다.

 

 

 

'Awesome Flutter Snippets' 라는 VSCode 확장을 설치해주자.

이걸 설치하면 위젯을 편리하게 생성할 수 있다.

 

 

 

splash_screen.dart에 'SplashScreen'이라는 클래스 생성해준다.

 

'st' 치고 statefulW 선택 (확장프로그램) >

클래스 이름 변경 > esc누르고 > StatefulWidget import 하기

 

하나하나 치고 있지 말고 이렇게 편하게 생성해주도록 하자.

 

 

import 'package:escape_anchovy/src/screen/splash/splash_screen.dart';
import 'package:flutter/material.dart';

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(home: SplashScreen());
  }
}

 

app.dart에는 SplashScreen을 실행시키는 MaterialApp이라는 위젯이 들어간다.

지금당장은 MyApp클래스에 상태변화가 없어서 Stateless위젯을 써도 되지만

추후에 상태변화가 필요해질거라 StatefulWidget을 썼다.

 

 

import 'package:escape_anchovy/src/app.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

 

main.dart에는 main함수만 남겨둔다.

 

 

 

스플래쉬 화면 구현

 

 

구현할 스플래쉬 화면이다.

멸치와 파도를 선택하고

 

 

2개의 레이어를 SVG로 Export 해준다.

 

이미지를 복사붙여넣기 한 다음 SVG로 추출한 파일을 사용하면

플러터에서 SVG가 출력되지 않는다.

 

그래서 SVG파일로 쓸려면 

펜툴이랑 패스파인더 가지고 직접 그려서 써야한다.

 

 

 

파일탐색기 열어서 다운로드 누른다음

svg폴더에 추출한 2개의 svg파일들을 복사해준다.

 

 

 

flutter_svg | Flutter package

An SVG rendering and widget library for Flutter, which allows painting and displaying Scalable Vector Graphics 1.1 files.

pub.dev

 

여기 들어가서 패키지 복사하고

 

 

 

pubspec.yaml에 flutter_svg 패키지 추가

 

 

 

왜인지는 모르겠는데 png와 svg경로도 pubsepc.yaml에

따로 추가해줘야 이미지가 불러와진다.

 

 

import 'package:flutter/material.dart';

class SplashController with ChangeNotifier {}

 

일단 임시적으로 controller클래스를 만들어놨다.

 

 

화면 레이아웃 구현

 

알아둬야 하는 Postioned 위젯의 특징

 

top: 10 < 위에서부터 10픽셀 간격을 준다.

bottom: 10 < 아래에서부터 10픽셀 간격을 준다.

 

a와 b라는 Positioned 코드 스니펫이 있을 때

a가 b보다 위에 작성됬다면 a는 b의 뒤쪽에 위치하게 된다.

 

Positioned에서 left값과 light값을 0으로 주면 가운데 정렬이 가능하다.

 

 

import 'package:escape_anchovy/res/text/styles.dart';
import 'package:escape_anchovy/src/screen/splash/splash_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';

class SplashScreen extends StatefulWidget {
  const SplashScreen({super.key});

  @override
  State<SplashScreen> createState() => _SplashScreenState();
}

class _SplashScreenState extends State<SplashScreen> {
  final _controller = SplashController();

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0XFFCCE9FF),
      body: AnimatedBuilder(
          animation: _controller,
          builder: (context, snapshot) {
            return _buildPage(context);
          }),
    );
  }

  Widget _buildPage(BuildContext context) {
    double screenHeight = MediaQuery.of(context).size.height;
    return Stack(
      children: [
        Positioned(
            top: screenHeight * 0.15,
            left: 0,
            right: 0,
            child: Text('ESCAPE\nENCHOVY',
                textAlign: TextAlign.center,
                style:
                    TextStyles.title.copyWith(color: const Color(0XFF8A848D)))),
        Positioned(
            bottom: 0,
            top: 0,
            right: 30,
            left: 0,
            child: SvgPicture.asset(
              'asset/svg/anchovy.svg',
              fit: BoxFit.scaleDown,
            )),
        Positioned(
            bottom: 0,
            left: 0,
            right: 0,
            child: SvgPicture.asset('asset/svg/wave.svg')),
        Positioned(
          bottom: screenHeight * 0.25,
          left: 0,
          right: 0,
          child: Center(
            child: Transform.rotate(
              angle: -3 * (3.141592653589793 / 180),
              child: const Text('운동을 위해 시간을 내지 않으면,\n병 때문에 시간을 내어야할지도 모른다.',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                      fontFamily: 'Pretendard',
                      fontSize: 18,
                      color: Colors.white,
                      fontWeight: FontWeight.w500)),
            ),
          ),
        ),
        const Positioned(
          bottom: 18,
          left: 0,
          right: 0,
          child: Center(
            child: Text('Created by Hamond', style: TextStyles.b3Regular),
          ),
        ),
      ],
    );
  }
}

 

멸치가 파도에서 올라오는 애니메이션을 구현할 예정이라

AnimatedBuilder로 화면 위젯을 감싸줬다.

 

빌더의 animation프로퍼티에 선언한 컨트롤러를 할당해주면서

컨트롤러의 변수값이 변경되면 즉시 반영되도록 해주었다.

 

화면 크기가 달라지는 걸 고려하여 

위 제목과 아래 명언은 메디아쿼리를 이용해 위치값을 조정하였다.

(screenHeight는 화면의 높이값으로 초기화된다.)

 

위젯의 배치는 Stack과 Postioned를 이용해서 해주면 된다.

Stack은 여러개의 Postioned 위젯을 겹치도록 배치하는 역할을 한다.

 

 

 

실행하면 이런식으로 잘 나온다.

 

 

랜덤한 명언 출력

final List<String> famousSaying = [
    '운동을 위해 시간을 내지 않으면\n병 때문에 시간을 내야할지도 모른다.',
    '독서는 마음을 위한 것이고\n운동은 몸을 위한 것이다.',
    '시간이 나서 운동하는게 아니라\n 시간을 내서 운동해야 한다.',
    '한계라고 느낄 때\n\'한 개\'를 더해야 성장한다.',
    '몸의 발전이\n마음의 발전을 이룬다.',
    '오늘 당신이 느끼는 고통은\n내일 당신이 느낄 힘이 될 것이다.',
    '정확하게 반복하고\n허세없이 운동해라.',
    '지금은 이 운동이 힘들지만\n언젠가는 워밍업이 될 것이다.',
    '인생은 당신이 가장 편안한 환경에서\n빠져나올 때부터 시작된다.',
    '그만두고 싶다는 생각이 들면\n왜 시작했는지 생각하여 보라.',
    '처음부터 다시 시작하는 것에 실증이 났다면\n하고 있는 일을 포기하지 않으면 된다.',
    '남들이 그만둘 때\n난 계속한다.',
    '포기는 선택이지\n운명이 아니다.'
  ];

  int randomNumber = Random().nextInt(12);

  double returnFamousSayingSize() {
    switch (randomNumber) {
      case 0:
      case 2:
      case 5:
      case 7:
      case 8:
      case 9:
        return 20;
      case 1:
      case 3:
      case 6:
        return 22;
      case 4:
        return 24;
      case 10:
        return 18;
      case 11:
        return 24;
      default:
        return 24;
    }
  }

 

SplashController클래스에 추가

 

글자크기를 조절하는 함수를 원래는

글자수를 기준으로 글자크기를 조절하는 방식으로 짜려했는데

 

그렇게하니까 줄바꿈이 이상하게 되어버려서

하나하나 돌려보면서 알맞은 사이즈로 설정해줬다.

 

 

Positioned(
          bottom: screenHeight * 0.25,
          left: 0,
          right: 0,
          child: Center(
            child: Transform.rotate(
              angle: -3 * (3.141592653589793 / 180),
              child: Text(_controller.famousSaying[_controller.randomNumber],
                  textAlign: TextAlign.center,
                  style: TextStyle(
                      fontFamily: 'Pretendard',
                      fontSize: _controller.returnFamousSayingSize(),
                      color: Colors.white,
                      fontWeight: FontWeight.w500)),
            ),
          ),
        ),

 

선언한 변수들을 Text 위젯에 알맞게 할당해주자

컨트롤러의 변수는 '_controller.변수명'으로 갖다쓸 수 있다.

 

 

위젯 애니메이션 구현

double anchovyTopPos = 60;

void moveUp() {
  Timer.periodic(const Duration(milliseconds: 50), (timer) {
    anchovyTopPos -= 2;
    if (timer.tick >= (3 * 1000) / 50) {
      timer.cancel();
    }
    notifyListeners();
  });
}

 

splashController 클래스에 추가

 

3초간 0.05초마다 위쪽으로 2px씩 움직이도록 했다. 

notifyListers()로 anchovyTopPos의 상태변화를 업데이트한다.

 

함수 내에서 변수값 변환시킬 때

notifyListeners() 함수는 필수로 들어간다. 

간단히 설명하자면 setState를 대체하는 함수라고 보면된다.

 

 

@override
  void initState() {
    super.initState();

    _controller.moveUp();
  }

 

initState에서 멸치를 움직이게 하는 함수를 실행

 

Positioned(
            bottom: 0,
            top: _controller.anchovyTopPos,
            right: 30,
            left: 0,
            child: SvgPicture.asset(
              'asset/svg/anchovy.svg',
              fit: BoxFit.scaleDown,
            )),

 

top의 값이 줄어들면 멸치는 위에서 아래로 올라오게 된다.

anchovyTopPos를 멸치의 y축값을 조정하는 top프로퍼티에 할당해주자.

 

 

스플래쉬 라우팅 구현

import 'package:escape_anchovy/res/text/styles.dart';
import 'package:escape_anchovy/src/screen/home/home_controller.dart';
import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: AnimatedBuilder(
          animation: _controller,
          builder: (context, snapshot) {
            return _buildPage(context);
          }),
    );
  }

  Widget _buildPage(BuildContext context) {
    return const Column(
      children: [
        Center(
          child: Text(
            'ㅎㅇ',
            style: TextStyles.h1Bold,
          ),
        )
      ],
    );
  }
}

 

홈 화면도 스플래쉬 화면과 똑같은 구조로 작성해주면 된다.

 

Timer(const Duration(seconds: 3), () {
      Navigator.pushReplacement(
          context, MaterialPageRoute(builder: (context) => const HomeScreen()));
    });

 

스플래쉬 화면의 initState에다가 

3초후에 홈화면으로 넘어가도록 하는 코드를 추가해주면 완성

 

 

결과물

 

저번글에서 기기 연결하는게 편하다 해놓고 왜 애뮬레이터 돌리고 있냐 싶을텐데

폰에서 찍은 gif는 안 움직여서 어쩔 수 없이 키게 됬다;;

 

대체 왜..?

 

 

 

깃허브

 

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
글 보관함