import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'STONE',
      home: Grade(),
    );
  }
}

class Grade extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.amber[700],
      appBar: AppBar(
        title: Text('STONE'),
        backgroundColor: Colors.amber[800],
        centerTitle: true,
        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/stone1.gif'),
                backgroundColor: Colors.amber[200],
                radius: 60.0,
              ),
            ),
            Divider(
              height: 60.0,
              color: Colors.grey[850],
              thickness: 0.5,
              endIndent: 30.0,
            ),
            Text('NAME',
            style: TextStyle(
              color: Colors.white,
              letterSpacing: 2.0
            ),),
            SizedBox(
              height: 10.0,),
            Text('STONE',
            style: TextStyle(
              color: Colors.white,
              letterSpacing: 2.0,
              fontSize: 28.0,
              fontWeight: FontWeight.bold
            ),),
            SizedBox(
              height: 30.0,
            ),
            Text('STONE POWER LEVEL',
              style: TextStyle(
                  color: Colors.white,
                  letterSpacing: 2.0
              ),),
            SizedBox(
              height: 10.0,),
            Text('14',
              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),
                SizedBox(
                  width: 10.0,
                ),
                Text('using lightsaber',
                style: TextStyle(
                  fontSize: 16.0,
                  letterSpacing: 1.0
                ),
                )
              ],
            ),
            Row(
              children: [
                Icon(Icons.check_circle_outline),
                SizedBox(
                  width: 10.0,
                ),
                Text('face hero tattoo',
                  style: TextStyle(
                      fontSize: 16.0,
                      letterSpacing: 1.0
                  ),
                )
              ],
            ),
            Row(
              children: [
                Icon(Icons.check_circle_outline),
                SizedBox(
                  width: 10.0,
                ),
                Text('fire flames',
                  style: TextStyle(
                      fontSize: 16.0,
                      letterSpacing: 1.0
                  ),
                )
              ],
            ),
            Center(
              child: CircleAvatar(
                backgroundImage: AssetImage('assets/stone1.gif'),
                radius: 40.0,
                backgroundColor: Colors.amber[700],
              ),
            )
          ],
        ),
      ),
    );
  }
}

d

d

assets 폴더를 새로 만든 뒤, assets 폴더에 이미지 파일을 옮겨 넣는다.

이후 pubspec.yaml 파일을 열어서

//assets:

// - ~~~~

//- ~~~~~

되어 있는 부분을 드래그해서 전부 선택한 뒤, Ctrl + /로 주석 처리를 제거한다.

그 다음에 적혀있는 파일 경로를 assets/파일이름.확장자명 으로 적어서 수정해주면, 위 코드가 제대로 작동한다.

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'STONE',
      home: Grade(),
    );
  }
}

class Grade extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.amber[700],
      appBar: AppBar(
        title: Text('STONE'),
        backgroundColor: Colors.amber[800],
        centerTitle: true,
        elevation: 0.0,
      ),
      body: Padding(
        padding: EdgeInsets.fromLTRB(30.0, 40.0, 0.0, 0.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('NAME',
            style: TextStyle(
              color: Colors.white,
              letterSpacing: 2.0
            ),),
            SizedBox(
              height: 10.0,),
            Text('STONE',
            style: TextStyle(
              color: Colors.white,
              letterSpacing: 2.0,
              fontSize: 28.0,
              fontWeight: FontWeight.bold
            ),),
          ],
        ),
      ),
    );
  }
}

오늘 작성할 코드이다.

중간까지는 대부분 비슷하지만, 몇가지 새로운 것들이 있다.

 

backgroundColor: Colors.amber[~~~]

배경 색상에 대한 내용인데, amber 색상에 [숫자] 값에 따라서 색상의 명도? 가 바뀐다. 색상인 amber에 마우스를 올려두면 각 수치별 색상을 확인할 수 있다.

 

appBar 안의 centerTitle은 타이틀 내용을 가운데로 정렬하는지 여부를 물어보는 것으로, true로 해두면 가운데 정렬이 된다.

 

elevation은 해당 위젯의 높이라고 생각하면 편하다. 해당 값에 0 이상의 수치가 들어가 있다면, 앱바 아래에 살짝 그림자가 지는 것을 볼 수 있다. 0.0으로 설정하여 높이 차가 있다는 느낌을 없앴다.

 

body에 Padding을 사용하여 정렬하였는데, EdgeInsets.fromLTRB라는 걸 사용했다.

LTRB는 각각 Left, Top, Right, Bottom을 의미하며, 패딩값(여백값)을 각각의 부분에 어느정도 부여할 지를 정하는 것이다.

이번 예제에서는 좌측과 상단에만 여백을 주고 나머지는 만지지 않았다.

 

이후 Column 정렬 안에서 CrossAxisAlignment.start를 사용했는데 이는 Column과 row에 대한 이해가 필요하다.

설명은 유용한 링크를 남길테니 해당 내용을 참조하길 바란다.

https://beomseok95.tistory.com/310#CrossAxisAlignment.start

 

Flutter - Row,Column정렬하기 (MainAxisAlignment, CrossAxisAlignment)

Flutter - Row, Column 정렬하기 (MainAxisAlignment, CrossAxisAlignment) axis는 중심선이라는 뜻입니다. crossaxis 횡축, mainaxis는 주축 이라는 뜻으로 해석할 수있습니다. 그렇다면 MainAxixAlignment와 Cro..

beomseok95.tistory.com

본 코드에서 쓰인 내용만 정리를 하자면

Column 정렬을 통해 이 내부에 속하는 위젯들은 세로로 정렬된다는 의미이다.

그 중에서도 crossAxisAlignment.start를 사용했으니 세로축을 기준으로 시작하는 부분, 즉 좌측으로 정렬한다는 의미이다.

그러므로 Column 내 위젯들은 세로로 순차적으로 배치되며, 각 요소들은 왼쪽에 붙어서 배치된다는 의미이다.

(crossAxisAlignment를 사용하지 않으면, STONE 위에 NAME이 STONE에 맞춰 가운데 정렬되어있다.)

 

이후 children을 통해 그 요소들을 선언하는데, Text 'NAME'과 Text 'STONE'이 선언된다.

 

color는 글자의 색상, letterSpacing은 자간 (글자 간 간격)이다.

 

fontWeight는 폰트 설정으로, STONE을 bold(굵은 글씨)로 설정했다.

 

그 사이에 껴있는 SizedBox는 아래 Stone 텍스트를 하나의 박스로 감싸서, 높이 값(Height)을 줘서 간격을 넓혔다.

 

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Character Card',
      home: MyCard(),
    );
  }
}

class MyCard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Stone'),

      ),
    );
  }
}

저번 포스팅에서 했던 것을 응용해서 몇개만 바꿔서 새로 작성한다.

텍스트나 타이틀, 클래스 명은 원하는대로 바꿔도 좋다.

이번에는 여러 저번보다 더 다양한 위젯을 배치해서 좀 더 그럴듯해 보이는 앱을 만들고자 한다.

우선 앱바의 코드부터 수정하자

class MyCard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Stone'),
        centerTitle: true,
        backgroundColor: Colors.redAccent,
        elevation: 0.0,
      ),
    );
  }
}

background color에 따라 앱바의 색이 달라지는데, 해당 색상을 원하는대로 설정해보자.

이제 body를 만질 차례다

class MyCard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Stone'),
        centerTitle: true,
        backgroundColor: Colors.redAccent,
        elevation: 0.0,
      ),
      body: Padding(
        padding: EdgeInsets.fromLTRB(30.0, 40.0, 0.0, 0.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Hello'),
            Text('Hello'),
            Text('Hello')
          ],
        ),
      ),
    );
  }
}

아래 Padding은 여백을 넣을 때 쓰는 위젯인데, body, 즉 앱 바 아래의 공간에서 해당 여백을 적용한다는 의미이다.

Column은 세로로만 정렬하는데, mainAxisAlignment는 그 중에서도 세로로 정렬할 때 상단, 중단, 하단을 분류하는데 쓰인다.

그럼 .center을 통해 중단으로 정렬한다는 의미가 되는데

실행을 해보면 알겠지만 좌측 중단에 텍스트들이 몰려있을 것이다.

이는 위에서 사용한 Padding에서 mainAxisAlignment를 사용했기 때문이다.

확인을 위해 padding 위젯을 삭제해 볼 것인데, 뒤에 //padding이 적힌 괄호부터 지우고 앞부분을 날리는게 좋다. 앞부분을 먼저 날리면 //padding 대신 아래의 //Scafold가 올라와 코드 줄 정렬이 더러워지기 때문이다.

class MyCard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Stone'),
        centerTitle: true,
        backgroundColor: Colors.redAccent,
        elevation: 0.0,
      ),
      body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('Hello'),
            Text('Hello'),
            Text('Hello')
          ],
        ),

    );
  }
}

이제 이 상태에서 Padding이 있던 자리에 Center위젯으로 대체해줄 것이다.

그렇게 하면 중단에 중앙으로 깔끔하게 정렬된 모습을 확인 할 수 있을 것이다.

body: Center(
child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Text('Hello'),
        Text('Hello'),
        Text('Hello')
      ],
    ),
  ),

 

padding 위젯은 가로축에는 한계치가 존재하지만 세로축에는 한계치가 존재하지 않는다. 

그러다 보니 Column위젯과 center 위젯이 만날 때 column위젯의 자식 위젯들에 대한 세로축 위치에 대해서는 관여 하지 않고, 그대신 현재 column위젯의 자식 위젯 세로축 높이에 자동으로 fix 되어버린다.

크게 body 내에서 사용되는 정렬 위젯을 Center, Column, mainAxisAlignment라고 가정하면

Center, column만 사용하면 최상단 중앙에 위치한다.

셋 다 사용하면 중단 중앙에 위치하는데, 만약 column대신 row를 사용한다면 세 텍스트가 가로로 이어져서 나올 것이다.

이처럼 여러 위젯을 사용해서 UI를 자유롭게 배치할 수 있다. 

 

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First app'),
      ),
    );
  }
}

MYApp 클래스 밖에 MyHomePage 클래스를 다시 만들어준다.

return Scaffold를 적고  내부 내용을 추가해서 앱 내부의 내용을 만들었는데

머티리얼 앱에서는 title: 'First app'이라고 했지만

홈페이지 클래스 안에서는 title : Text('First app')이라고 사용했다.

이제 실제 모바일 환경에서 제대로 작동하는지 확인해보자

위의 핸드폰 마크가 찍혀있는 곳에서 Nexus 5X~~~로 핸드폰 기종이 찍힌 것을 고른다. 없다면 Refresh를 눌러보자

선택을 한뒤 플레이버튼과 flutter attach(핸드폰에 플러터 아이콘이 그려진 버튼)을 누르면 약간 시간이 지난 뒤 핸드폰 화면에 작성한 내용을 볼 수 있을 것이다.

이제 제대로 나오는 것을 확인 했으니 앱 바 아래 부분을 채워보자.

아래처럼 MyHomePage코드를 바꾼다

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First app'),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            Text('Hello'),
            Text('Hello'),
            Text('Hello')
          ],
        ),
      ),
    );
  }
}

 

위와같이 화면이 나온다면 잘 따라한 것이다.

* 위의 MyApp클래스 작업 중에 home: MyHomePage()를 home: Scaffold()로 작성해도 된다. 결과는 똑같이 나온다.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'First app',
      theme: ThemeData(
        primarySwatch: Colors.blue
      ),
      home: Scaffold( appBar: AppBar(
        title: Text('First app'),
      ),
        body: Center(
          child: Column(
            children: <Widget>[
              Text('Hello'),
              Text('Hello'),
              Text('Hello')
            ],
          ),
        ),
      );
    );
  }
}

이처럼 코드의 양에 따라 분리하거나 합치는 등의 작업으로 더 유연하게 코딩이 가능할 것이다.

 

 

 

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(

    );
  }
}

MyApp 클래스를 생성하는 기본 코드이다.

stl 을 쳐서 나오는 stless를 통해 틀을 한번에 만들어서 작업 할 수 있다.

stf도 마찬가지다.

이후 MaterialApp의 내용을 채워 넣을 건데

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'First app',
      theme: ThemeData(
        primarySwatch: Colors.blue
      ),
    );
  }
}

title 제목

theme: ThemeData() 테마 정보

ThemeData 내 primarySwatch 특정 색의 음영들을 기본 색상으로 지정하여 사용할 것임을 의미함

이후 ThemeData 바깥에서 

home: MyHomePage()라고 작성한다.

home은 앱이 정상적으로 실행되었을 때 처음으로 화면에 나타나는 내용이다.

그 내용으로MyHomePage라는 클래스를 사용하겠다는 뜻이다.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'First app',
      theme: ThemeData(
        primarySwatch: Colors.blue
      ),
      home: MyHomePage(),
    );
  }
}

 

1. Flutter 프로젝트 폴더의 구성

2. 코드의 기본 내용

3. flutter 앱 실행

 

기존에 생성했던 플러터 프로젝트를 확인해보면

main.dart 이외에도 여러 폴더와 파일들이 있다.

이 중 test 폴더 안의 pubspec.yaml 파일은 프로젝트의 메타데이터를 정의하고 관리하는 것으로, 앞으로 많이 만질 파일이다.

flutter, dart의 버전이나 각종 써드파티 라이브러리 등을 이곳에서 정의한다.

그리고 그 위의 android나 ios 폴더는 각 플랫폼에 맞게 앱을 배포하기 위한 정보들을 가지고 있는 폴더이다.

여러 폴더들이 있지만 그 중 lib 폴더의 main.dart를 가장 많이 사용할 것이니 일단 main 파일을 확인한다.

기본적으로 생성된 내용을 ctrl+A로 선택하고 지워준다.

처음부터 작성을 시작할 것인데, 제일 처음에 할 일은 flutter ~~라는 라이브러리를 임포트하는 것인데, 이를 해야 ㅍ플러터의 여러 기본 기능들을 사용할 수 있다. 

*머티리얼 디자인은 구글에서 제시한 모바일, 데스크톱 등 여러가지 디바이스를 아우리는 일관된 디자인을 위해서 구글이 제시하는 가이드라인임

첫줄에 import ' ' 을 적고 내부에 fm이라고 쓰면 자동완성 기능으로

package:flutter/material.dart라는 파일이 나온다. 선택하고 엔터를 치면

import 'package:flutter/material.dart';

과 같이된다. 첫번째 라이브러리 임포트는 이렇게 된다.

이제 메인 함수를 만들어 준다.

void main() => runApp(app)

를 작성하면 이는 메인함수가 다른 함수를 호출한다는 의미로 사용되는데

runApp()이라는 함수를 호출하겠다. 최상위 함수 중 하나로 한번만 호출해도 된다.

괄호 안에 app이라는 인자(Arguments)를 사용했는데, 이 인자는 꼭 widget이어야 합니다.

함수가 호출될 때 필수적으로 어떤 값을 같이 호출해야하는데, 이 값은 widget이어야 한다는 의미입니다.

그러므로 main함수가 runApp함수를 호출하고 그 인자로 app이라는 위젯을 사용한 것입니다.

이후 app 대신 MyApp()을 넣을 것인데, 이렇게 하면

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

가 될 것이다.

MyApp에 에러가 뜰 것인데, 당연하다. 우리가 앞으로 만들 위젯의 이름이기 때문이다

 

* 함수 첫글자는 소문자

* 클래스의 첫글자는 대문자

 

 

플러터에서의 위젯은 눈에 보이는 UI들을 구성하는 모든 기본 단위 요소입니다.

핸드폰을 켜면 나오는 버튼, 아이콘, 텍스트 등이 모두 위젯이라고 칭해진다고 생각하면 됩니다.

또한 눈에 보이지 않는 요소들도 위젯으로 칭하는데, 웹 프로그래밍을 조금 해봤으면 알 지도 모르는 center, padding, column 등의 레이아웃을 정의하는 요소들도 위젯이라고 합니다.

결론은 모든 것이 위젯이라고 한다고 할 수도 있는데 플러터에서는 화면 구성요소와 레이아웃 등 여러가지 요소들이 만나서 만들어진 앱도 위젯이라고 합니다.

추가로 UI를 설정하는데 있어서 드래그앤 드롭으로 설정할 수 없고 오로지 코드로만 작성해야한다는 단점이 있습니다.

위젯은 크게 Stateless widget, Stateful Widget으로 나눠지며, 트리 구조로 구성되어 있습니다.

Stateful은 Value(값)을 지속적으로 추적, 보존하는 것을 말하고

Stateless는 이전 상호작용의 어떠한 값도 저장하지 않음을 의미합니다.

간단하게는 Stateless Widgets는 상대가 없는 정적인 위젯

Stateful Widgets는 계속 움직임이나 변화가 있는 위젯으로 생각하시면 됩니다.

 

Stateless Widgets는 세가지 특징을 갖는다.

1. 스크린상에 존재만 할 뿐 아무 것도 하지 않음

2. 어떠한 실시간 데이터도 저장하지 않음

3. 어떤 변화(모양, 상태)를 유발시키는 value값을 가지지 않음

 

Stateful Widgets

1. 사용자의 인터렉션에 따라서 모양이 바뀜 (체크박스)

2. 데이터를 받게 되었을 때 모양이 바뀜(텍스트 필드)

 

Flutter Widget tree

1. Widget들은 tree 구조로 정리될 수 있음

2. 한 Widget 내에 얼마든지 다른 Widget들이 포함될 수 있음.

3. Widget은 부모 위젯과 자식 위젯으로 구성

4. Parent widget을 widget container라고 부르기도 함.

 

 

명령 프롬포트 실행 후 flutter doctor라고 친 뒤 엔터를 누르면

정상적으로 설정이 완료되있는지 체크해주는데, 본인은 에러가 발생했다.

 cmdline-tools component is missing

우선 cmdline-tools component is missing은 안드로이드 스튜디오에서 SDKmanager 위치를 못찾아서 발생하는 문제라고 한다.

이는 

> flutter config --android-studio-dir "안드로이드 스튜디오 위치"
> flutter config --android-sdk "안드로이드 SDK 위치"

> flutter config --android-studio-dir "C:/Programs/Android/Android Studio"
> flutter config --android-sdk "D:/Android/Sdk"

을 cmd 창에 쳐서해결할 수 있다고한다. 아래는 직접 입력한 예시이므로, 해당 위치를 찾아서 입력해줄 것

이를 진행했더니 Unable to locate Android SDK. 라는 에러가 새로 발생했는데

확인해보니 경로 설정이 잘못되었다. 정확한 위치를 찾기 힘들다면

다음과 같이 안드로이드 스튜디오 -Customize - all setting - sdk 검색 - Andorid SDK location 경로 확인

해당 경로를 복사해서 다시 cmd에서

> flutter config --android-sdk "해당 경로"

을 실행하면 정상적으로 진행될 것이다.

허나 !로 표시된 이슈가 아직도 해결되지 않았다.

[!] Android toolchain - develop for Android devices (Android SDK version 29.0.2)
    ! Some Android licenses not accepted.  To resolve this, run: flutter doctor --android-licenses

이렇게 출력되는 부분이 있는데, 이는

flutter doctor --android-licenses

이 부분을 복사해서 실행하면 몇가지 y n 으로 체크하는 내용이 나오는데, 다 y를 눌러서 넘어가면 된다.

다시 flutter doctor를 실행시켜서 v 이외에 다른 오류가 있는지 찾아본다.

뜨지않는다면 이제 정말 설정이 완료되었다.

 

이제 드디어 완전한 상태가 되었으니 플러터 프로젝트를 만들어볼 때입니다.

 

new flutter 프로젝트를 클릭하고, 좌측에서 flutter을 선택해준 뒤, SDK 경로를 설치 폴더로 지정해줍니다.

그 다음 NEXT를 누르고 경로 설정을 한 뒤 열면 main.dart 파일과 함께 정상적으로 열립니다.

여기까지 하셨으면 첫번째 flutter 프로젝트 생성이 끝납니다.

+ Recent posts