며칠 나를 괴롭힌 상태관리 관련 문제가 해결됐다..

 

역시나 해결하고나니 왜 이걸 생각못했나 생각이들기도 하고

기초가 아직 부족하단 생각이 많이 들었다..

 

이제 다음 코드를 구현중 경험한 오류를 한가지 공유하려한다.

역시나 구글이 최고다 없는게 없다.

  • Expanded()
  • Flexible()
  • Positioned()

문제는 보통 위 3가지에서 발생된다.

Expanded()와 Flexible() 는 Row(), Colum(), flex() 로 한번 감싸주면되고

Positioned()는 Stack()으로 한번 감싸면 된다.

 

내 코드에선 해당 오류를 해결하지 않아도 잘 작동되어서 무시하고 있었는데.

해결하니 속시원하니 콘솔창이 깨끗해졌다.

 

맥북 에어 m2가 왔다.....

첫 MAC OS인 만큼 적응이 너무 어렵다...

받자마자 뭐부터 만져야 할지도 몰랐다..

 

편하게 바꾸기보단 OS 그 자체를 느껴보고자

순정 상태로 사용해보려고 한다.

 

Flutter 설치를 끝내고 Ios 애뮬을 돌리는데 너무 심장이 떨렸다...

이렇게 빠르다니....

 

맥북은 천천히 익히도록 하고 밀린 Flutter 부터 학습해보자

이번에는 토이프로젝트를 조금씩 완성해보려고한다.

무엇을 할지 모른다면 역시 생각나는 작은것 부터 처리하는게 좋은거같다.

Splash 화면이다. 앱을 처음에 실행하면 나오는 로딩 페이지라고 생각하면 된다.

앱 로고가 아직 없기에 아이폰에 있는 내 이모지를 활용했다.

 

일단 첫번째로 기본 프로젝트를 만들어주자

 

https://pub.dev/packages/flutter_native_splash

위 페이지의 라이브러리를 이용하오니 설치 해주자.

pub get을 해주고

프로젝트 root 디렉토리에 flutter_native_splash.yaml 파일을 만들어주자

 

 

 

 

 

 

 

안에 들어갈 코드는 위 페이지에 나와있다.

코드중 내가 수정한 부분은 단 두줄이다.

백그라운드 컬러 및 로고 이미지 경로

기기의 모드(다크/라이트)에 따라 불러오는 로고를 다르게 설정할 수 있다. 이건 나중에 하도록 하겠다.

 

 

 

 

다음은 main.dart를 수정해보자

import 'package:flutter_native_splash/flutter_native_splash.dart';

를 import 해준뒤

메인 함수에 두줄을 추가한다.

해당 코드를 아래 위젯에 추가해준다.

해당 splash가 3초간 지속되고 첫 페이지로 들어가게 된다.

 

 

 

 

 

 

 

 

 

 

 

 

이로써 Splash 이미지는 구현 완료이다.

참 간단하지만 뭔가 있어보이는? 그런 라이브러리인거같다.

 

다음으로는 get을 이용해 하단 네비게이터바를 구현하여 다중 페이지를 구현하도록할것이다.

 

날이 너무 춥다....

가을옷 꺼낸지 일주일 된거같은데 벌써 겨울옷을 꺼내야할듯하다.

 

점점 플러터를 공부할수록 자신감이 붙으면서

프로젝트를 빨리 시작하고싶다.

그러나 아직 터무니없이 부족하단걸 느끼기에...

기본기 부터 탄탄하게 해야겠다.

 

오늘은 PageView를 해보자

이처럼 스크롤을 하여 페이지를 넘기는것이다.

이번 시간엔 라이브러리 import없이 진행한다.

 

class myapp extends StatelessWidget {
  const myapp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'onboarding Page',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: welcomePage(),
    );
  }
}

main.dart 파일 코드이다.

 

각 페이지에 사용할 이미지를 프로젝트 파일에 넣고 pub get 해주자.

welcomePage.dart 파일을 만들어 코드를 작성하자

 

먼저 statefull widget을 만든다.

body 안에 PageView.builder을 작성해준다.

 

안에 scrollDirection: Axis. 을 하면 3가지가 나오는데

horizontal - 가로 스크롤

vertical -  세로 스크롤

이렇게 이해하면 된다.

 

이제 assets한 이미지 갯수만큼 페이지를 만들어야하기에 먼저 이미지를 리스트화 시켜주자.

사진과 같이 코드를 작성하면 된다,

" " 에 들어갈 텍스트는 이미지소스의 경로이다.

이렇게 하면 길이가 3인 images 라는 String 형 리스트를 만든것이다.

 

itemCount - 이미지가 총 3개이니 3 or images.length 로 하면 되지만 가독성을 위해 후자로 해준다.

 

itemBuilder - 각 페이지를 구성 해준다.

image 지정을 위해 AssetImage(images[index]) 해주자.

이렇게 작성하면 인덱스 0부터 끝까지 페이지마다 구성된다.

지정된 이미지 사이즈가 다를 수 있으니 높이와 너비를 맞춰주자.

 

 

 

 

 

 

 

오늘 배워볼 부분은 앱의 기초인 페이지 만들기이다.

그동안 main 페이지 하나만 만들어 그안에서만 기능을 구현하였다..

이번엔 여럿 페이지를 만들어 페이지간 이동을 해보자

 

페이지간 이동은 기본적으로 스택을 기본으로한다.

페이지 위에 다른페이지를 올려 화면에 띄우는 방식이다.

해당 부분은 유튜브에 영상이 많으니 한번 이해하고 오는것이 좋을꺼같다.

 

페이지는 screenA, B, C 총 3가지 이며 한 페이지에 다른 두개의 페이지에 넘어갈수 있도록 구현해볼것이다.

프로젝트/lib/  경로에 screenA, B, C 3가지 파일을 새롭게 만들어보자

 

main.dart 에는 위처럼 screenA를 홈으로 지정해준다.

이제 A,B,C 각 페이지 코드는 정말 비슷하므로 screenA를 예로 들겠다.

- appbar

- body

  - center

    - colum

      - ElevatedButton

      - ElevatedButton

이런 구조로 되어있다.

여기서 한가지 알아야할점이 있다면 좀 오래된 강의를 보면 처음보는 버튼 이름이 나와있을것이다.

위 ElevatedButton도 이전에는 RaisedButton였다.

아래 그림을 참고하자

출처 : https://dkswnkk.tistory.com/50#recentComments

Navigator  push코드를 보자

push - 화면 최상단에 띄우는것이다. (스택 맨위)

뒤쪽 MaterialPageRoute는 안드로이드에서 제공하는 에니메이션?? 이라고한다.

그래서 CupertinoPageRoute로 변경하여 실행해보면 화면 넘김 애니메이션이 좀 다르다.

pop - 화면을 지우는것이다.

예를 들어 A -> B -> A 의 경우 push를 2번( A -> B , B -> A) 이렇게 사용할수 있지만 이런경우 스택이 총 3개 쌓인다.
push 1회  pop 1회 이렇게 구성하면 B 화면을 push했다 pop 하기 때문에 스택이 1개 만 쌓이게 되는것이다.

Navigator 코드 첫번째 context는 왼쪽에 보이는 코드 위쪽 나와있는 context와 일치해야하며

두변째 context는 상관없다 따라서 _ 를 해도좋다.

 

 

 

 

앱을 구동해보면 앱바 왼쪽에 뒤로가기 버튼이 생기는것을 볼 수 있다.

이게 pop역활을 하는것이다.

따라서 앱바가 있는경우 pop을 구현할 필요는 없다..

 

이제 다중 페이지를 쉽게 구현하기 위해 initialRoute를 사용하여 구현해보자

main.dart의 코드이다.

home: 대신 initinalRoute를 사용했고

첫 페이지는 / 를 사용해야한다.

 

initinalRoute: '/'

/ 페이지를 home으로 지정하겠다. 이다.

 

아래 routes 는 앱에서 사용될 페이지들을

지정해주는 코드이다.

A, B, C 차례대로 /, /b, /c  이렇게

이름으로 지정해줬다.

 

 

 

 

 

 

 

 

 

 

Navigator.pushNamed
를 이용하여 작성하면 된다.

이번 시간에는 버튼을 학습했다.

단순한 버튼은 아니고 클릭하면 메시지가 뜨게 하는 버튼이다.

Material은 기본적으로 snackbar를 제공하여 메시지를 띄울수있지만 안이쁘다...

난 이쁜걸 원한다...

바로 Toast라는 것이다.

먼저 설치 방법부터 알아보자

1. pubspec.yaml 수정

프로젝트 파일중 pubspec.yaml에 들어가

fluttertoast: ^8.0.9를 넣어준다.

그 다음 위쪽 Pub get을 눌러 load 해준다.

 

2, main.dart 수정

import 'package:fluttertoast/fluttertoast.dart';

맨 윗줄에 해당 문구 삽입

 

이렇게 진행 하면 사용준비는 완료이다.

 

이번 시간에 만들 예제이다.

appbar - 버튼 3개 텍스트 1개

body - 텍스트 1개 버튼2개

로 구성되어있다.

 

appbar에 있는 버튼은 Toast 기본 설정으로 띄울것이며

body에 있는 Custom Button은 Toast-Custom을 이용

Success Button는 motion-toast를 사용할것이다.

*motion-toast는 별도의 설정이 필요하다... 검색 ㄱㄱ

 

appBar: AppBar(
  title: Center(child: Text("appBar & Toast Test")),
  elevation: 0.0,
  leading: IconButton(
      icon: Icon(Icons.menu),
      onPressed: () {
        showToast('이것은 메뉴입니다.');
      }),
  actions: [
    IconButton(
      icon: Icon(Icons.shopping_cart),
      onPressed: () {
        showToast('이것은 장바구니입니다.');
      },
    ),
    IconButton(
      icon: Icon(Icons.search_rounded),
      onPressed: () {
        showToast('이것은 검색입니다.');
      },
    ),
  ],
),

 

leading - appbar 맨 왼쪽에 자리잡는 위젯이다.

action - appbar 맨 오른쪽에 자리잡는 위젯이다.

 

body: Container(
  height: double.infinity,
  width: double.infinity,
  child: Column(
    mainAxisAlignment: MainAxisAlignment.center,
    crossAxisAlignment: CrossAxisAlignment.center,
    children: [
      const Text("Toast 메시지 예제", style: TextStyle(fontSize: 20,),
      ),
      const SizedBox(height: 10,),
      Container(width: 200, height: 50,
        child: ElevatedButton(
          onPressed: () {_showToast();},
          child: const Text("Custom Button", style: TextStyle(fontSize: 20,),),
        ),
      ),
      const SizedBox(height: 10,),
      Container(width: 200, height: 50,
        child: ElevatedButton(
          onPressed: () {_displaySuccessToast(context);},
          child: const Text("Success Toast", style: TextStyle(fontSize: 20,),),
        ),
      ),
height: double.infinity,
width: double.infinity,

mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,

이 두가지는 항상 쓰려고한다. 왜냐하면 위젯들이 센터로 자리잡을 수 있게 한다.

중간중간 SizeBox를 통해 위젯마다 공간을 주었다.

 

이제 Toast 부분을 살펴보자

 

void showToast(String msg2) {
  Fluttertoast.showToast(
    msg: msg2,
    toastLength: Toast.LENGTH_SHORT,
    gravity: ToastGravity.BOTTOM,
    backgroundColor: Colors.red,
    textColor: Colors.white,
  );
}
onPressed: () {
  showToast('이것은 장바구니입니다.');
},

이것은 appbar에 들어간 Toast 기본 버튼이다.

Fluttertoast.showToast를 이용하면 쉽게 구현가능하다.

두번째 코드는 호출 코드이다.

코드와 같이 String을 넣어줄수있다.

 

late FToast fToast;

@override
void initState() {
  super.initState();
  fToast = FToast();
  fToast.init(context);
}
_showToast() {
  Widget toast = Container(
    padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
    decoration: BoxDecoration(
      borderRadius: BorderRadius.circular(25.0),
      color: Colors.redAccent,
    ),
    child: Row(
      mainAxisSize: MainAxisSize.min,
      children: const [
        Icon(Icons.check, color: Colors.white,),
        SizedBox(
          width: 10.0,
        ),
        Text('이것은 커스텀 버튼입니다.', style: TextStyle(color: Colors.white,),),
      ],
    ),
  );

  fToast.showToast(
    child: toast,
    gravity: ToastGravity.BOTTOM,
    toastDuration: Duration(seconds: 1),
  );
}
onPressed: () {
_showToast();
},

Toast를 커스텀 할 수 있는 코드이다.

기본으로 해도 충분히 깔끔하게 나오지만 좀 더 아쉬우면 해당 기능으로 구현해보자.

여럿 값을 만져주면서 어떤 부분이 어떻게 변경되는지 쉽게 확인가능하다.

 

import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:motion_toast/motion_toast.dart';

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

class appBar extends StatelessWidget {
  const appBar({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'appBar & Toast',
      theme: ThemeData(primarySwatch: Colors.lightGreen),
      home: firstpage(),
    );
  }
}

class firstpage extends StatefulWidget {
  const firstpage({Key? key}) : super(key: key);

  @override
  State<firstpage> createState() => _firstpageState();
}

class _firstpageState extends State<firstpage> {

  late FToast fToast;

  @override
  void initState() {
    super.initState();
    fToast = FToast();
    fToast.init(context);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: const Color(0xFF69abce),
        appBar: AppBar(
          title: Center(child: Text("appBar & Toast Test")),
          elevation: 0.0,
          leading: IconButton(
              icon: Icon(Icons.menu),
              onPressed: () {
                showToast('이것은 메뉴입니다.');
              }),
          actions: [
            IconButton(
              icon: Icon(Icons.shopping_cart),
              onPressed: () {
                showToast('이것은 장바구니입니다.');
              },
            ),
            IconButton(
              icon: Icon(Icons.search_rounded),
              onPressed: () {
                showToast('이것은 검색입니다.');
              },
            ),
          ],
        ),
        body: Container(
          height: double.infinity,
          width: double.infinity,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              const Text("Toast 메시지 예제", style: TextStyle(fontSize: 20,),
              ),
              const SizedBox(height: 10,),
              Container(width: 200, height: 50,
                child: ElevatedButton(
                  onPressed: () {_showToast();},
                  child: const Text("Custom Button", style: TextStyle(fontSize: 20,),),
                ),
              ),
              const SizedBox(height: 10,),
              Container(width: 200, height: 50,
                child: ElevatedButton(
                  onPressed: () {_displaySuccessToast(context);},
                  child: const Text("Success Toast", style: TextStyle(fontSize: 20,),),
                ),
              ),
            ],
          ),
        ));
  }

  void showToast(String msg2) {
    Fluttertoast.showToast(
      msg: msg2,
      toastLength: Toast.LENGTH_SHORT,
      gravity: ToastGravity.BOTTOM,
      backgroundColor: Colors.red,
      textColor: Colors.white,
    );
  }

  void _displaySuccessToast(context) {
    MotionToast.success(
      title: Text("Success"),
      description: Text("This is Success Toast"),
    ).show(context);
  }

  _showToast() {
    Widget toast = Container(
      padding: const EdgeInsets.symmetric(horizontal: 15.0, vertical: 10.0),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(25.0),
        color: Colors.redAccent,
      ),
      child: Row(
        mainAxisSize: MainAxisSize.min,
        children: const [
          Icon(Icons.check, color: Colors.white,),
          SizedBox(
            width: 10.0,
          ),
          Text('이것은 커스텀 버튼입니다.', style: TextStyle(color: Colors.white,),),
        ],
      ),
    );

    fToast.showToast(
      child: toast,
      gravity: ToastGravity.BOTTOM,
      toastDuration: Duration(seconds: 1),
    );
  }
}

 

오늘 진행한 화면 페이지

보이는 사진을 구역별로 나눠 코드를 보여주고 설명하도록 하겠다..

우선적으로 이번 예제에선 데이터에 변화가 없기에 Stateless Widget만 사용한다.

material을 import 시켜주고

기본적인 위젯 코드를 짜주자 외울필요없이 stl만 치면 나온다. 

위 코드가 항상 시작할때 필요한 기본 코드이다.

맨위 appbar이다.

appbar를 보면 배경색이 있는데 보통강의를 보면 Colors.red 처럼 지정된 색을 사용하였지만 나는 hex코드를 이용했다.

hex코드는 Color(0xFF-------) 이렇게 사용하면 된다. 3번째줄의 색 코드는 앱 전체 배경색이고 

appbar이 배경색 코드는 아랫쪽이다.

title는 센터로 한번 감싸주어 Text와 backgroundColor 두개 child를 갖고있다.

아래 elevation은 appbar와 body의 경계 음영이다. 숫자를 키워서 적용하면 이해가능할것이다.

body는 기본적으로 column으로 구성된다.

처음 위젯은 CircleAvatar이다.

해당 위젯또한 Center로 한번 감싸

x축의 센터로 맞춰준다.

백그라운드 이미지는 해당 프로젝트 루트에 저장했기에 AssetImage를 이용해 줬다.

인터넷 url을 통한 방법도 있지만 많을경우 앱이 무거워질수가있다.

radius는 원의 크기이다.

해당 위젯은 Divider로 만들었다.

height는 위아래 여백이며 위 30 아래 30의 공간을 둔다는 의미이다.

color은 hex코드를 이용하지않고 지정된 색을 사용하였다.

thickness는 선의 두깨를 의미한다. 숫자를 변경해보면 이해가능하다.

endIndent는 오른쪽 여백을 의미하며 해당 코드가 없으면 선이 오른쪽에 붙는걸 확인할 수 있을것이다.

4개의 텍스트 위젯을 사용했다.

letterSpacing - 문자간 간격을 의미한다.

fontsize - 글자 크기

fontWeight  - 글자 굵기를 설정할 수 있다.

해당 위젯을보면 가로 2개씩 세로 3개로 구성되어있다.

Row 로 2개(아이콘,텍스트) 묶어 구성하면 된다.

Icon은 Icon(Icons. 을 찍으면 아이콘 종류가 많이 나온다. 원하는걸 쓰면 된다.

SizeBox는 Icon과 Text 사이 여백을 의미한다.

기본적으로 위젯을 디자인하는 방법은 정말 다양하다. 여백을 주는 방법 또한 여러가지이다.

 

얼추 기본 구성은 감이 잡혔지만 계속해서 예제를 통해 더 익혀야겠다.

아래는 풀 코드이다.

 

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'BBANTO',
      home: Grade(),
    );
  }
}

class Grade extends StatelessWidget {
  const Grade({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color(0xFF69abce),
      appBar: AppBar(
        title: Center(
          child: Text(
            'ID card',
            style: TextStyle(
              fontSize: 30.0,
            ),
          ),
        ),
        backgroundColor: Color(0xff5D8FA9),
        elevation: 0.0,
      ),
      body: Padding(
         padding: EdgeInsets.fromLTRB(30.0, 40.0, 0.0, 0.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Center(
              child: CircleAvatar(
                backgroundImage: AssetImage('assets/2.png'),
                radius: 80.0,
              ),
            ),
            Divider(
              height: 60.0,
              color: Colors.grey[850],
              thickness: 1.5,
              endIndent: 30.0,
            ),
            Text(
              'NAME',
              style: TextStyle(
                color: Colors.white,
                letterSpacing: 2.0,
                fontSize: 20.0,
                fontWeight: FontWeight.bold,
              ),
            ),
            SizedBox(
              height: 10.0,
            ),
            Text(
              'Coding Master',
              style: TextStyle(
                  color: Colors.white,
                  letterSpacing: 2.0,
                  fontSize: 28.0,
                  fontWeight: FontWeight.bold),
            ),
            SizedBox(
              height: 30.0,
            ),
            Text(
              'score',
              style: TextStyle(
                color: Colors.white,
                letterSpacing: 2.0,
                fontSize: 20.0,
                fontWeight: FontWeight.bold,
              ),
            ),
            SizedBox(
              height: 10.0,
            ),
            Text(
              '3.5',
              style: TextStyle(
                  color: Colors.white,
                  letterSpacing: 2.0,
                  fontSize: 28.0,
                  fontWeight: FontWeight.bold),
            ),
            SizedBox(
              height: 30.0,
            ),
            Row(
              children: [
                Icon(
                  Icons.check_circle_outline,
                  size: 25.0,
                ),
                SizedBox(
                  width: 10.0,
                ),
                Text(
                  '1111111111',
                  style: TextStyle(
                    fontSize: 25.0,
                    letterSpacing: 1.0,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
            Row(
              children: [
                Icon(
                  Icons.check_circle_outline,
                  size: 25.0,
                ),
                SizedBox(
                  width: 10.0,
                ),
                Text(
                  '2222222222',
                  style: TextStyle(
                    fontSize: 25.0,
                    letterSpacing: 1.0,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
            Row(
              children: [
                Icon(
                  Icons.check_circle_outline,
                  size: 25.0,
                ),
                SizedBox(
                  width: 10.0,
                ),
                Text(
                  '3333333333',
                  style: TextStyle(
                    fontSize: 25.0,
                    letterSpacing: 1.0,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

+ Recent posts