정보
- 업무명 : [Flutter] 쉽게 배우는 플러터 앱 개발 실무 06강
- 작성자 : 이상호
- 작성일 : 2025.01.18
- 설 명 :
- 수정이력 :
공유 파일
- 원본 파일
- 작업 파일
기본 Widget II
학습목표
- ListView Widget의 사용법과 사용시의 이점을 이해하고 구현해 봅니다.
- 그외 알아두면 유용한 Widget들이 어떤 것들이 있는지 알아봅니다.
- if 조건 표현식, 삼항 연산자 및 for 반복 표현식을 활용하여 Widget을 구현하는 방법을 이해하고 구현해 봅니다.
TextField Widget
- TextField Widget은 문자열의 입력을 받는 Widget입니다.
- first_flutter 프로젝트를 다시 연후 아래 코드를 화면의 최상단 Widget으로 추가해 봅시다.
// main.dart 함수
... 생략 ...
TextField( // 추가된 TextField Widget
decoration: InputDecoration(
labelText: "필드이름",
border: OutlineInputBorder(),
),
),
Container(
width: double.infinity,
alignment: Alignment.center,
color: Colors.yellow,
child: const Text(
'You have pushed the button this many times:',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.pink,
fontSize: 40,
fontStyle: FontStyle.italic,
),
),
),
... 생략 ...
- 앱을 실행해 보면 화면의 상단에 입력을 위한 필드가 추가되어 있는 것을 알 수 있습니다. 그런데 입력 필드가 화면의 행을 모두 채우고 있습니다.
- 입력 필드의 길이는 SizedBox Widget을 사용하여 통제할 수 있습니다.
// main.dart 함수
... 생략 ...
SizedBox( // SizedBox Widget으로
width: 200, // 입력필드의 길이를 200 논리적 픽셀로 지정
child: TextField(
decoration: InputDecoration(
labelText: "필드이름",
border: OutlineInputBorder(),
),
),
),
... 생략 ...
- 앱을 다시 실행해 보면 입력 필드의 길이가 지정한 만큼 축소되는 것을 확인할 수 있습니다.
- 텍스트 필드에 입력한 문자열이 화면에만 보이지 않고 변수에서 값을 받아 오거나 변수에 값을 저장하는 방법을 알아 보겠습니다. TextField Widget과 연결하여 사용할 변수는 TextEditingController 객체 변수로 정의한 후 TextField Widget의 controller 속성으로 지정해 줍니다.
// main.dart 함수
... 생략 ...
class _MyHomePageState extends State<MyHomePage> {
TextEditingController textEditingController = TextEditingController(); // 콘트롤러 추가
@override
void initState() {
// TODO: implement initState
super.initState();
textEditingController.text = "입력 필드의 초기값";
}
... 중략 ...
SizedBox(
width: 200,
child: TextField(
controller: textEditingController, // TextField와 Controller 연결
decoration: InputDecoration(
labelText: "필드이름",
border: OutlineInputBorder(),
),
),
),
... 생략 ...
- 그후 프로그램의 코드에서 textEditingController.text = "입력 필드의 초기값";와 같은 할당문을 사용하면 텍스트 필드에 값이 나타납니다.
- 화면에 입력한 값을 가져다 사용할 때에는 TextField Widget과 연결된 변수의 값을 textEditingController.text와 같은 형식으로 코드에서 가져다 사용하면 됩니다.
// main.dart 함수
... 생략 ...
SizedBox(
width: 200,
child: TextField(
// 값 변경 후 Enter를 치면 textEditingController.text에 저장된 값을 print
onSubmitted: (_) {print(textEditingController.text);},
controller: textEditingController,
decoration: InputDecoration(
labelText: "필드이름",
border: OutlineInputBorder(),
),
),
),
... 생략 ...
- TextField Widget에 TextField Changed라고 입력한 후 Enter를 치면 안드로이드 스튜디오의 하단 창에 입력한 값이 출력되는 것을 확인할 수 있습니다.
- 이와 같이 화면에서 TextField 입력을 받아 사용하기 위해서는 화면의 TextField와 연결되어 변수 역할과 제어 역할을 해 주는 제어기(TextEditingController)의 중재가 필요합니다. 아래의 그림을 참조바랍니다. TextEditingController 외에도 이와 유사한 역할을 해 주는 다양한 (TextEditingController)들이 존재하는데 기본적인 원리와 개념과 의미는 동일하며 사용법은 약간의 차이가 있을 뿐 유사합니다.
표현식과 연산자를 활용한 Widget
- Flutter는 if나 for와 같은 표현식(Expression)이나 삼항 연산자(Operatr)를 사용하여 Widget를 구성하는 방법을 제공합니다.
- if 조건 표현식
// main.dart 함수
... 생략 ...
class _MyHomePageState extends State<MyHomePage> {
bool contentLoaded = false; // 콘텐츠가 로드되었는지 여부를 나타내는 변수
TextEditingController textEditingController = TextEditingController();
... 중략 ...
if (contentLoaded ) Text("콘텐츠가 로드되었습니다.") // 콘텐츠 로드된 경우
else CircularProgressIndicator(), // 콘텐츠 로드 중인 경우
SizedBox(
width: 200,
child: TextField(
onSubmitted: (_) {print(textEditingController.text);},
controller: textEditingController,
decoration: InputDecoration(
labelText: "필드이름",
border: OutlineInputBorder(),
),
),
),
... 생략 ...
- 앱을 실행해 보면 contentLoaded 변수에 false가 저장되어 있어서 로딩 중이라는 의미로 원형 프로그래스 바가 출력되는 것을 확인할 수 있습니다.
- 플러스(+) 버튼을 누를 때 _count 변수를 증가시킴과 동시에 contentLoaded 변수의 값을 true로 변경시켜 보겠습니다.
// main.dart 함수
... 생략 ...
void _incrementCounter() {
setState(() {
_counter++;
contentLoaded = true; // 콘텐츠가 로드되었다고 변수 설정
});
}
... 생략 ...
- 앱을 다시 실행해 보면 플러스(+) 버튼을 누를 때 원형 프로그래스 바가 “콘텐츠가 로드되었습니다.”라는 텍스트로 출력되는 것을 확인할 수 있습니다.
삼항 연산자
- 이번에는 if 표현식(Expression)을 삼항 연산자(Operator)로 간결하게 표현해 보겠습니다.
// main.dart 함수
... 생략 ...
// if (contentLoaded ) Text("콘텐츠가 로드되었습니다.")
// else CircularProgressIndicator(),
// 삼항 연산자로 변경
contentLoaded ? Text("콘텐츠가 로드되었습니다.") : CircularProgressIndicator(),
SizedBox(
width: 200,
child: TextField(
onSubmitted: (_) {print(textEditingController.text);},
controller: textEditingController,
decoration: InputDecoration(
labelText: "필드이름",
border: OutlineInputBorder(),
),
),
),
... 생략 ...
- 앱의 실행결과는 동일합니다.
for 반복 표현식
- ListView Widget에서 사용했던 SimpleListView 예제를 화면의 상단에 추가해 보겠습니다.
// main.dart 함수
... 생략 ...
class _MyHomePageState extends State<MyHomePage> {
// ListView Widget에 반복하여 보여줄 리스트 변수
var strList = ["1번째 항목","2번째 항목","3번째 항목","4번째 항목","5번째 항목",
"6번째 항목","7번째 항목","8번째 항목","9번째 항목","10번째 항목",
"11번째 항목","12번째 항목","13번째 항목","14번째 항목","15번째 항목",
"16번째 항목","17번째 항목","18번째 항목","19번째 항목","20번째 항목"
];
bool contentLoaded = false;
... 중략 ...
// ListView Widget의 크기를 SizedBox의 크기로 제한
SizedBox(
height: 200,
child: ListView.builder(
itemCount: strList.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
// 오류 발생을 예방하기 위하여 주석 처리
// Navigator.of(context).push(
// MaterialPageRoute(builder: (_) => SimpleListViewDetailPage(id: index, password: strList[index]))
// );
},
child: Card(
child: Center(
child: Text(strList[index],
style: TextStyle(fontSize: 40),
),
),
),
);
}
),
),
// if (contentLoaded ) Text("콘텐츠가 로드되었습니다.")
// else CircularProgressIndicator(),
contentLoaded ? Text("콘텐츠가 로드되었습니다.") : CircularProgressIndicator(),
... 생략 ...
앱을 실행해 보면 오른쪽 화면과 같이 나타납니다.
- 이 코드를 for 반복 표현식을 사용하여 코딩해 보겠습니다. 아래 코드와 같이 리스트가 나타나게 하는 것까지는 단순한 형식으로 구현이 가능한데, itemBuilder의 index 인자를 사용할 수 없는 한계가 있습니다.
// main.dart 함수
... 생략 ...
// ListView Widget의 크기를 SizedBox의 크기로 제한
SizedBox(
height: 200,
// child: ListView.builder(
// itemCount: strList.length,
// itemBuilder: (context, index) {
// return GestureDetector(
// onTap: () {
// // Navigator.of(context).push(
// // MaterialPageRoute(builder: (_) => SimpleListViewDetailPage(id: index, password: strList[index]))
// // );
// },
// child: Card(
// child: Center(
// child: Text(strList[index],
// style: TextStyle(fontSize: 40),
// ),
// ),
// ),
// );
// }
// ),
child: ListView( // ListView.builder가 ListView로 변경됨
children: [ // itemCount와 itemBuilder 속성대신 children 속성 사용
for (var element in strList) // for 반복 표현식 사용
Card( // Card Widget이 strList 변수의 요소마다 생성됨
child: Center(
child: Text(element,
style: TextStyle(fontSize: 40),
),
),
),
],
),
),
... 생략 ...
- 그래서 Dart 언어의 문장(Statement)으로 이 문제를 해결하려고 하면 아래 화면과 같이 오류가 발생합니다. 여기서 사용하는 if와 for 표현식(Expression)과 삼항 연산자(Operator)는 return 문장안에서 사용되어 Dart 언어의 문장과 달리 약식의 제한적인 문법만 허용하기 때문입니다. Dart의 표현식 기반 문법은 간결하면서도 강력한 기능을 제공하여, 웹 프로그래밍에서 사용되는 ASP, JSP, Thymeleaf와 유사한 동적 콘텐츠 처리를 구현할 수 있습니다.
DropdownButton Widget
- DropdownButton Widget은 사전에 정해진 값들 중에서 선택하여 입력을 받는 Widget입니다. 아래 코드를 화면의 최상단 Widget으로 추가해 봅시다.
// main.dart 함수
... 생략 ...
class _MyHomePageState extends State<MyHomePage> {
var selectedElement = "요소2"; // DropdownButton 요소의 초기값
var strList = ["1번째 항목","2번째 항목","3번째 항목","4번째 항목","5번째 항목",
"6번째 항목","7번째 항목","8번째 항목","9번째 항목","10번째 항목",
"11번째 항목","12번째 항목","13번째 항목","14번째 항목","15번째 항목",
"16번째 항목","17번째 항목","18번째 항목","19번째 항목","20번째 항목"
];
... 중략 ...
SizedBox(
width: 200,
child: Center(
child: DropdownButton<String>( // DropdownButton Widget 추가
value: selectedElement, // DropdownButton의 선택과 변수를 연결
onChanged: (String? newValue) { // DropdownButton 선택시 이벤트 처리기
print(newValue); // 인자가 Null로 넘어올 수 있어 ?로 허용
setState(() { // DropdownButton에 연결된 변수값 상태 변경
selectedElement = newValue!; // 값이 Null 아닌 것을 !로 확인후 사용
});
},
items: [
for (var element in ["요소1","요소2","요소3"]) // for 반복 표현식
DropdownMenuItem<String>( // DropdownButton의 요소 Widget 추가
value: element,
child: Text(element),
),
],
),
),
),
... 생략 ...
- 앱을 실행하면 화면 상단에 DropdownButton Widget이 나타나는 것을 확인할 수 있으며 value 속성으로 지정한 selectedElement 변수의 값이 초기값으로 나타나는 것을 확인할 수 있습니다.
- DropdownButton Widget을 클릭하거나 탭하면 for 표현식으로 추가한 DropdownMenuItem<String> Widget들이 선택 가능한 목록으로 나타나는 것을 확인할 수 있습니다.
- 요소를 선택하면 선택한 요소가 DropdownButton Widget에 나타나고, 실행창에도 print되는 것을 확인할 수 있습니다.
- TextField와 DropdownButton Widget 외에 Radio<T>와 RichText 등의 입력을 위한 Widget들이 다양하게 존재하니 필요할 때 찾아서 사용하기 바랍니다.
SingleChildScrollView Widget
- SingleChildScrollView Widget은 화면 하나로 제한된 크기를 Scroll하여 확장하게 만들어 줍니다.
// main.dart 함수
... 생략 ...
@override
Widget build(BuildContext context) {
double imageWidth = (MediaQuery.of(context).size.width - 30) / 3;
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: SingleChildScrollView( // Center Widget을 SingleChildScrollView으로 감싸줌
child: Center(
child: Column(
// mainAxisAlignment 변경 : spaceAround -> start
//mainAxisAlignment: MainAxisAlignment.spaceAround,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
... 생략 ...
- 앱을 실행하면 화면 상단에 하단에 화면이 깨지는 현상이 사라지고, 아래로 Scroll할 수 있는 것을 확인할 수 있습니다.
[실습] 계산기
- 프로젝트 이름 : calculator
- 설명 : BottomNavigationBar를 사용하여 4개의 주화면을 구성한 후 단계별로 계산기를 만듭니다.
import 'package:flutter/material.dart';
import 'calculator_page1.dart';
import 'calculator_page2.dart';
import 'calculator_page3.dart';
import 'calculator_page4.dart';
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: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var pages = [
CalculatorPage1(),
CalculatorPage2(),
CalculatorPage3(),
CalculatorPage4(),
];
int _selectedNaviIndex = 0;
_onBottomNavigationItemTapped(index) {
setState(() {
_selectedNaviIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: pages[_selectedNaviIndex],
bottomNavigationBar: BottomNavigationBar(
//showSelectedLabels: false,
//showUnselectedLabels: false,
currentIndex: _selectedNaviIndex,
selectedItemColor: Colors.blue,
unselectedItemColor: Colors.grey,
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
onTap: _onBottomNavigationItemTapped,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.looks_one),
label: "Calculator1",
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_two),
label: "Calculator2",
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_3),
label: "Calculator3",
),
BottomNavigationBarItem(
icon: Icon(Icons.looks_4),
label: "Calculator4",
),
],
),
);
}
}
- 1단계 : 사칙연산(/,*,-,+)을 수행하는데 연산자와 숫자의 입력이 적절한지 확인하지 않고 우선 계산되는 기능을 구현해 봅니다. 연산자가 틀린 경우 오류가 발생하거나 동작하지 않는 한계가 있습니다.
- 2단계 : 연산자가 사칙연산(/,*,-,+)을 벗어나는 경우 하단에 오류 메세지를 출력하도록 개선합니다. 여전히 입력된 숫자가 틀린 경우 오류가 발생하거나 동작하지 않는 한계가 있습니다.
- 3단계 : 사칙연산(/,*,-,+)의 입력이 틀리지 않도록 드롭 다운 버튼(DropdownButton Widget)으로 구현하여 실수를 방지함으로써 입력 검증을 하지 않습니다. 입력한 숫자가 적절하지 않은 오류 메세지를 보여 주고 다시 입력하게 합니다.
- 4단계 : 숫자와 사칙연산(/,*,-,+)의 입력이 틀리지 않도록 버튼으로 구현하여 입력하는 수식을 상단에 나타나게 한 후 = 버튼을 누르면 계산하게 합니다. 입력한 수식이 적절하지 않은 C(Clear) 버튼을 눌러 다시 입력하게 합니다. 수작업 입력을 배제하여 실수가 방지되나 여전히 수식이 잘못될 위험이 남아 있어서 try~catch 예외처리로 수식의 오류를 잡아 냅니다.
expressions: ^0.2.5 패키지를 사용합니다. pubspec.yaml에 아래와 같이 등록한 후 Pub Get 버튼을 누릅시다.
코드 팩토리의 플러터 프로그래밍 - 10장 만난지 며칠 U&I (240페이지)
- 프로젝트 이름 : u_and_i
- 설명 : 하트를 눌러서 처음 만난 날을 설정하면 오늘이 D+몇일인지를 보여 줍니다. 처음으로 iOS의 쿠퍼티노 디자인을 사용해 봅니다.
- 이미지와 폰트를 저자의 깃허브(https://github.com/codefactory-co/flutter-golden-rabbit-novice-v2.git)에서 다운받아 images와 fonts 폴더에 복사해 넣은 후 pubspec.yaml에 등록 후 Pub Get
- 이 예제에 폰트를 등록하는 전형적인 pubspec.yaml 설정이 나오고 있음. Regular와 Light 폰트는 weight을 지정하지 않고 있고, Medium과 Bold 폰트는 weight를 각각 500과 700으로 지정하고 있음
# pubspec.yaml의 코드
assets:
- images/
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# <https://flutter.dev/to/resolution-aware-images>
# For details regarding adding assets from package dependencies, see
# <https://flutter.dev/to/asset-from-package>
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
fonts:
- family: parisienne
fonts:
- asset: fonts/Parisienne-Regular.ttf
- family: sunflower
fonts:
- asset: fonts/Sunflower-Light.ttf
- asset: fonts/Sunflower-Medium.ttf
weight: 500
- asset: fonts/Sunflower-Bold.ttf
weight: 700
- 화면을 만들어 봅시다.
- Hear Icon을 누르면 쿠퍼티노 날짜 다이어로그가 화면의 하단에 나오도록 해 봅시다. import 'package:flutter/cupertino.dart';로 import하는 showCupertinoDialog() 쿠퍼티노 함수를 사용하여 쉽게 구현할 수 있습니다.
웹프로그래밍(개인 포트폴리오)
학습목표
- 플러터로 웹프로그래밍을 하는 방법을 이해하고 구현해 봅니다.
- 개인의 포트폴리오 사이트를 웹으로 구현하여 Github에 배포해 봅니다.
개인 포트폴리오 사이트 개요
플러터 웹프로그램으로 개인 포트폴리오 사이트를 만들어 볼 것 입니다. 아래와 같은 견본 화면들을 참조하여 각자를 소개할 사이트를 설계해 보시기 바랍니다. 여기서의 목적은 플러터로 화면을 만드는 것이 아니라 웹프로그래밍을 하는 것이니 공수를 많이 들이지 말고 개략적으로 만든 후 향후 보완해 가시기 바랍니다.
홈(Home) 페이지의 견본
프로젝트(Project) 페이지의 견본
연락처(Contact) 페이지의 견본
프로젝트 생성 및 설정
- my_portfolio 프로젝트를 생성합니다. 플랫폼으로 Web을 꼭 선택합시다.
- 필요한 이미지들을 images 폴더에 저장합니다. 그리고 pubspec.yaml 파일에 다음과 같이 강조 혹은 주석 처리된 코드와 같이 수정 및 추가합시다.
- email_button.png
- phone_button.png
# To add assets to your application, add an assets section, like this:
assets: // 주석 해제
- images/ // 이미지가 저장된 폴더
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
- Web 개발을 위하여 pubspec.yaml 파일에 다음과 같이 강조 및 주석 처리한 코드를 추가합시다.
dependencies:
flutter:
sdk: flutter
flutter_web_plugins: // 웹 개발을 위한 플러그인
sdk: flutter // flutter 플러그인 지정
cupertino_icons: ^1.0.8
웹프로그램 뼈대 코드 만들기
- lib 폴더 하부에 projects_page.dart 파일을 생성하고 다음과 같이 코드합시다.
import 'package:flutter/material.dart';
class ContactPage extends StatelessWidget {
const ContactPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("연락처 페이지"),
),
);
}
}
- lib 폴더 하부에 contact_page.dart 파일을 생성하고 다음과 같이 코드합시다.
import 'package:flutter/material.dart';
class ContactPage extends StatelessWidget {
const ContactPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("연락처 페이지"),
),
);
}
}
- main.dart 파일의 코드에서 아래 코드에서 강조 처리된 코드를 수정 혹은 추가합시다.
import 'package:flutter/material.dart';
// 웹프로그램을 개발하기 위한 패키지 import
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
// 프로젝트와 연락처 페이지 import
import 'projects_page.dart';
import 'contact_page.dart';
void main() {
usePathUrlStrategy(); // 웹방식의 라우팅을 사용하도록 설정
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: 'Flutter Demo Home Page'),
);
*/
return MaterialApp(
initialRoute: '/',
onGenerateRoute: (routeSettings) {
// ?? 연산자는 앞에 지정된 값이 Null인 경우에 뒤의 값을 사용하도록 하는 역할을 함
final uri = Uri.parse(routeSettings.name ?? '/');
final path = uri.path;
Widget page;
switch (path) {
case "/":
page = MyHomePage();
case "/projects":
page = ProjectsPage();
case "/contact":
page = ContactPage();
default:
return null;
}
return PageRouteBuilder(
settings: routeSettings,
pageBuilder: (_, __, ___) => page,
transitionDuration: Duration.zero,
);
},
);
}
}
class MyHomePage extends StatefulWidget {
/* title 속성을 넘기지 않도록 코드를 수정
const MyHomePage({super.key, required this.title});
final String title;
*/
const MyHomePage({super.key});
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
/* 플러터 프레임워크가 제공한 코드를 주석 처리하고 웹의 홈페이지로 변경
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
*/
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text("홈 페이지"),
),
);
}
}
- 웹프로그램의 뼈대가 되는 코드들을 완성했으니 안드로이드 스튜디오 상단에서 실행할 디바이스로 이뮬레이터를 선택하지 않고 Chrome 혹은 Edge를 선택한 후 앱을 실행합니다.
- 웹프로그램의 뼈대가 되는 코드들을 완성했으니 안드로이드 스튜디오 상단에서 실행할 디바이스로 이뮬레이터를 선택하지 않고 Chrome 혹은 Edge를 선택한 후 앱을 실행합니다. 그러면 포트 번호가 자동으로 지정되며 ‘/’ 라우트로 지정된 홈 페이지가 나타나는 것을 확인할 수 있습니다.
- 웹페이지의 주소에 /projects로 지정하면 ‘/projects’ 라우트로 지정된 프로젝트 페이지가 나타나는 것을 확인할 수 있고
- 웹페이지의 주소에 /contact로 지정하면 ‘/contact’ 라우트로 지정된 연락처 페이지가 나타나는 것을 확인할 수 있습니다.
웹화면의 네비게이션바 만들기
- 웹프로그래밍할 때 네비게이션바의 역할은 플러터의 앱바를 사용하여 만들수 있는데, 앱바는 홈 페이지와 프로젝트 페이지와 연락처 페이지에 공통적으로 반영되기 때문에 appBar 코드를 함수화하겠습니다. lib 폴더 하단에 common_appbar.dart 파일을 만들어 아래 코드와 같이 AppBar Widget을 반환값으로 가지는 buildAppBar 함수를 만들어 봅시다.
import 'package:flutter/material.dart';
AppBar buildCommonAppBar() {
return AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"홈",
style: TextStyle(
color: Colors.purple,
fontSize: 16,
fontWeight: FontWeight.bold, // 선택된 강조 Weight
),
),
SizedBox(width: 37),
Text(
"프로젝트",
style: TextStyle(
color: Colors.grey,
fontSize: 16,
fontWeight: FontWeight.normal, // 선택되지 않은 일반 Weight
),
),
SizedBox(width: 37),
Text(
"연락처",
style: TextStyle(
color: Colors.grey,
fontSize: 16,
fontWeight: FontWeight.normal, // 선택되지 않은 일반 Weight
),
),
],
),
);
}
- 그리고 main.dart 파일, projects_page.dart 파일 및 contact_page.dart 파일에 아래 코드에 강조 및 주석 처리된 것과 같이 추가합니다.
// main.dart 파일의 코드
import 'package:flutter/material.dart';
import 'package:flutter_web_plugins/flutter_web_plugins.dart';
import 'projects_page.dart';
import 'contact_page.dart';
// commonAppBar 콤포넌트 import
import 'common_appbar.dart';
... 생략 ...
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: buildCommonAppBar(), // 공통 AppBar 추가
body: Center(
child: Text("홈 페이지"),
),
);
}
}
// projects_page.dart 파일의 코드
import 'package:flutter/material.dart';
// commonAppBar 콤포넌트 import
import 'common_appbar.dart';
class ProjectsPage extends StatelessWidget {
const ProjectsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: buildCommonAppBar(), // 공통 AppBar 추가
body: Center(
child: Text("프로젝트 페이지"),
),
);
}
}
// contact_page.dart 파일의 코드
import 'package:flutter/material.dart';
// commonAppBar 콤포넌트 import
import 'common_appbar.dart';
class ContactPage extends StatelessWidget {
const ContactPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: buildCommonAppBar(), // 공통 AppBar 추가
body: Center(
child: Text("연락처 페이지"),
),
);
}
}
- 그후 앱을 다시 실행해 보면 아래 화면들과 같이 나타납니다.
- 그런데 화면의 앱바 즉 네비게이션바의 색상이 선택된 페이지와 일치하지 않습니다. 그래서 코드를 아래와 같이 각각 수정해 보겠습니다.
// common_appbar.dart 파일의 코드
import 'package:flutter/material.dart';
// 네비게이션할 페이지들을 열거형으로 정의합니다.
enum NavigationPages {
home,
projects,
contact,
}
// 선택된 페이지를 인자로 받아옴
AppBar buildCommonAppBar(NavigationPages selectedPage) {
return AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"홈",
style: TextStyle(
// 선택된 홈 페이지의 폰트 색상을 보라색으로, 그렇지 않은 경우 회색으로 변경함
color: selectedPage == NavigationPages.home ? Colors.purple : Colors.grey,
fontSize: 16,
// 선택된 홈 페이지의 폰트를 강조하고, 그렇지 않은 경우 일반 두께로 변경함
fontWeight: selectedPage == NavigationPages.home ? FontWeight.bold : FontWeight.normal,
),
),
SizedBox(width: 37),
Text(
"프로젝트",
style: TextStyle(
// 선택된 프로젝트 페이지의 폰트 색상을 보라색으로, 그렇지 않은 경우 회색으로 변경함
color: selectedPage == NavigationPages.projects ? Colors.purple : Colors.grey,
fontSize: 16,
// 선택된 프로젝트 페이지의 폰트를 강조하고, 그렇지 않은 경우 일반 두께로 변경함
fontWeight: selectedPage == NavigationPages.projects ? FontWeight.bold : FontWeight.normal,
),
),
SizedBox(width: 37),
Text(
"연락처",
style: TextStyle(
// 선택된 연락처 페이지의 폰트 색상을 보라색으로, 그렇지 않은 경우 회색으로 변경함
color: selectedPage == NavigationPages.contact ? Colors.purple : Colors.grey,
fontSize: 16,
// 선택된 연락처 페이지의 폰트를 강조하고, 그렇지 않은 경우 일반 두께로 변경함
fontWeight: selectedPage == NavigationPages.contact ? FontWeight.bold : FontWeight.normal,
),
),
],
),
);
}
// main.dart 파일의 코드
... 생략 ...
@override
Widget build(BuildContext context) {
return Scaffold(
// 선택된 홈 페이지를 인자로 넘겨줌
appBar: buildCommonAppBar(NavigationPages.home),
body: Center(
child: Text("홈 페이지"),
),
);
}
}
// projects_page.dart 파일의 코드
import 'package:flutter/material.dart';
import 'common_appbar.dart';
class ProjectsPage extends StatelessWidget {
const ProjectsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// 선택된 프로젝트 페이지를 인자로 넘겨줌
appBar: buildCommonAppBar(NavigationPages.projects),
body: Center(
child: Text("프로젝트 페이지"),
),
);
}
}
// contact_page.dart 파일의 코드
import 'package:flutter/material.dart';
import 'common_appbar.dart';
class ContactPage extends StatelessWidget {
const ContactPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// 선택된 연락처 페이지를 인자로 넘겨줌
appBar: buildCommonAppBar(NavigationPages.contact),
body: Center(
child: Text("연락처 페이지"),
),
);
}
}
- 이제 네비게이션바에서 선택된 페이지의 색상과 두께가 보라색에 강조된 폰트로 나타나는 것을 확인할 수 있습니다.
네비게이션(화면이동) 기능 만들기
- 플러터 앱에서 아래 코드와 같이 정의된 웹 방식의 라우팅을 사용하는 경우
- Navigator.of(context).pushNamed("/"); 문장으로 홈 페이지로 이동할 수 있고, Navigator.of(context).pushNamed("/projects"); 문장으로 프로젝트 페이지로 이동할 수 있으며, Navigator.of(context).pushNamed("/contact"); 문장으로 연락처 페이지로 이동할 수 있습니다.
- 그래서 buildCommonAppBar() 함수가 context를 인자로 받아올 수 있게 수정하고 네비게이션바에 위치한 홈, 프로젝트 및 연락처 텍스트 위젯(Text Wdiget)를 클릭할 때 Navigator.pushNamed() 메소드를 사용하여 클릭된 페이지로 이동하도록 수정하여야 합니다.
// common_appbar.dart 파일의 코드
import 'package:flutter/material.dart';
enum NavigationPages {
home,
projects,
contact,
}
// context를 추가적인 인자로 받아옴
AppBar buildCommonAppBar(BuildContext context, NavigationPages selectedPage) {
return AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 클릭하면 홈 페이지로 이동
GestureDetector(
onTap: () { Navigator.of(context).pushNamed("/"); },
child: Text(
"홈",
style: TextStyle(
color: selectedPage == NavigationPages.home ? Colors.purple : Colors.grey,
fontSize: 16,
fontWeight: selectedPage == NavigationPages.home ? FontWeight.bold : FontWeight.normal,
),
),
),
SizedBox(width: 37),
// 클릭하면 프로젝트 페이지로 이동
GestureDetector(
onTap: () { Navigator.of(context).pushNamed("/projects"); },
child: Text(
"프로젝트",
style: TextStyle(
color: selectedPage == NavigationPages.projects ? Colors.purple : Colors.grey,
fontSize: 16,
fontWeight: selectedPage == NavigationPages.projects ? FontWeight.bold : FontWeight.normal,
),
),
),
SizedBox(width: 37),
// 클릭하면 연락처 페이지로 이동
GestureDetector(
onTap: () { Navigator.of(context).pushNamed("/contact"); },
child: Text(
"연락처",
style: TextStyle(
color: selectedPage == NavigationPages.contact ? Colors.purple : Colors.grey,
fontSize: 16,
fontWeight: selectedPage == NavigationPages.contact ? FontWeight.bold : FontWeight.normal,
),
),
),
],
),
);
}
- 그리고 buildCommonAppBar() 함수를 호출할 때 context를 인자로 넘겨 주도록 수정합니다.
// main.dart 파일의 코드
... 생략 ...
@override
Widget build(BuildContext context) {
return Scaffold(
// context를 추가적인 인자로 넘겨줌
appBar: buildCommonAppBar(context, NavigationPages.home),
body: Center(
child: Text("홈 페이지"),
),
);
}
}
// projects_page.dart 파일의 코드
import 'package:flutter/material.dart';
import 'common_appbar.dart';
class ProjectsPage extends StatelessWidget {
const ProjectsPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// context를 추가적인 인자로 넘겨줌
appBar: buildCommonAppBar(context, NavigationPages.projects),
body: Center(
child: Text("프로젝트 페이지"),
),
);
}
}
// contact_page.dart 파일의 코드
import 'package:flutter/material.dart';
import 'common_appbar.dart';
class ContactPage extends StatelessWidget {
const ContactPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
// context를 추가적인 인자로 넘겨줌
appBar: buildCommonAppBar(context, NavigationPages.contact),
body: Center(
child: Text("연락처 페이지"),
),
);
}
}
- 프로그램을 실행해 보면 이제 웹브라우저의 주소창에 URL을 입력하는 방식이 아니라 네비게이션바의 텍스트를 클릭하여 페이지간의 이동이 되는 것을 확인할 수 있습니다.
폴더 리팩토링
- lib 폴더 하부를 보면 소스 코드 파일들이 종류에 상관없이 섞여 있습니다.
- lib 폴더 하부에 pages 폴더와 widgets 폴더를 만들어 소스코드를 종류별로 리팩토링(Refactoring)하여 분류해 둡시다.
- 폴더를 분류한 후 소스 코드에서 해당 파일을 참조하는 코드들도 분류된 폴더에 맞게 자동으로 변경되는 것을 확인해 보기 바랍니다.
[실습] 자신의 경력을 소개하는 my_portfolio 웹을 완성하세요.
- 시간을 많이 들이지 말고 간략히 개발했다가 향후 개선해 가시기 바랍니다.
GitHub로 포트폴리오 웹프로그램 배포
- github.com로 이동하여 로긴합니다. 계정이 없으면한 후 로긴합니다.
- 리포지터리 이름을 깃헙아이디.github.io(예: yongjaeahn.github.io)와 같이 부여하여 Public Repository를 만들어줍니다.
- 안드로이드 스튜디오 터미널 창에서 flutter build web 명령어를 수행합니다. 명령어 수행이 완료되면 배포할 웹프로그램의 폴더가 build 폴더 하단에 web 폴더로 생성되어 있는 것을 확인할 수 있습니다.
- 앞에서 생성한 깃헙아이디.github.io 리포지토리로 이동하여 uploading an existing file 메뉴를 클릭합니다.
- web 폴더 내에 있는 파일들을 드래그하여 업로드 합니다.
- 업로드가 끝나면 화면 하단의 Commit changes를 클릭합니다.
- 리포지터리의 상단에서 Settings 메뉴를 클릭한 후 좌측 메뉴에서 Pages를 클릭한 후 https://yongjaeahn.github.io 와 같은 형태의 웹사이트 링크나 Visit Site 버튼을 클릭하여 GitHub에 배포된 포트폴리오 웹사이트를 방문할 수 있습니다.
- GitHub에 배포되는 포트폴리오 웹사이트는 개인이나 팀이 자신의 경력, 프로젝트, 기술 역량을 효과적으로 보여주기 위한 디지털 공간입니다. 이 사이트는 여러 가지 용도로 활용되며, 특히 개발자나 디자이너에게 매우 유용합니다.
- Github에 공개된 개인 프로필 사례 : https://rodrigob.github.io
참고 문헌
[논문]
- 없음
[보고서]
- 없음
[URL]
- 없음
문의사항
[기상학/프로그래밍 언어]
- sangho.lee.1990@gmail.com
[해양학/천문학/빅데이터]
- saimang0804@gmail.com
'프로그래밍 언어 > Flutter' 카테고리의 다른 글
[Flutter] 쉽게 배우는 플러터 앱 개발 실무 05강 (0) | 2025.01.11 |
---|---|
[Flutter] 쉽게 배우는 플러터 앱 개발 실무 04강 (0) | 2025.01.04 |
[Flutter] 쉽게 배우는 플러터 앱 개발 실무 03강 (2) | 2024.12.28 |
[Flutter] 쉽게 배우는 플러터 앱 개발 실무 FAQ 및 Tips (0) | 2024.12.24 |
[Flutter] 쉽게 배우는 플러터 앱 개발 실무 02강 (0) | 2024.12.21 |
최근댓글