반응형

Flutter 공부한지 3일 만에 계산기 앱을 만들다 보니 너무 두서 없이 만들어 졌습니다. 앱을 만들어 가는 과정에서 MVVM 패턴이라던가 상태관리라는 것을 적용할 필요가 있다는 것을 알았습니다. 

상태관리 package를 적용하여 프로그램을 수정하면서 이번 포스팅을 계속해야 하나 망설이다가 제어쪽 포스팅을 한참만에 올리게 되었습니다. 어차피 이번 포스팅은 초보자 관점에서 만들어 진것이니 제목 그대로 초보자가 만든 개념으로 포스팅은 마무리 하고 최종 수정된 프로그램은 별도로 공개 하는 방향으로 정하게 되었습니다. 

 

그래서 이번에 최종적으로 계산기 제어 코드를 올리면서 아울러 상태관리와 함께 지난 포스팅에 빠져 있던 몇가지 버튼 기능들도 추가 하여 코드를 수정하고 공개 하게 되었습니다. 

 

수정된 부분

1. MVVM 패턴 적용 : 파일도 패턴에 맞추어 분리 시키고 나름 MVVM 패턴을 적용해 보았습니다. 

2. 상태관리 팩키지 적용 : 상태관리 패키지 2가지를 따로 따로 적용하여 코드를 만들어 보았습니다.

   적용 패키지 (Provider, GetX )

3. 계산기의 몇가지 추가 버튼에 대한 기능을 추가 하였습니다. 

 

코드는 GitHub 에 올려 놓았고 provider 과 GetX 두개로 Branche 시켜 두었습니다. 

Main Branche 가 Provider 를 적용한 것이고 다른 하나는 GetX 입니다. 아래 Git 에서 소스 코드를 확인할 수 있습니다. 

 

https://github.com/Sihwan-Kim/calculator_flutter.git

반응형
반응형

계산기 만들기 마지막 포스팅으로 실제 계산기의 버튼을 누르고 연산을 하는 부분의 코드를 만듭니다. 

일단 컨트롤에 사용될 변수를 전역변수로 선언을 합니다. main 함수 위에 선언해 주시면 됩니다. 

var displayNumber = '0';
var makeNumber = '' ;
var selectedOperator = '−';
var displayFontSize = 80.0 ;
var pointExist = false ;

var firstNumber = 0.0 ;
var secondNumber = 0.0 ;

var _disignPage = new DesignPage() ;

 

컨트롤은 크게 3개의 함수로 구성되어 있습니다. 

 

1. 숫자키 입력 : 숫자키와 소숫점을 눌렀을 때 발생되는 알고리즘에 대한 함수 입니다. 숫자는 처음입력시에는 문자열의 형태로 저장을 하고 이후 연산할 때 double 형의 숫자로 변경하도록 프로그램 했습니다. 

void _numberOnPressed(String st)  		// 숫자키 입력 이벤트 함수 
{
	bool inputAdd = true ; 

	if(makeNumber.length < 9)  // 숫자는 9자리 까지만 
	{
		if(st == '.') // 소숫점이 눌려졌을 경우 
		{
			if(makeNumber.isEmpty == true)
			{ 
				makeNumber += '0.';
				inputAdd = false ;
			}
			else
			{
				if(pointExist == true) inputAdd = false ; // 소숫점이 없을 경우만 추가
			}

			pointExist = true ;
		}
		else if(st == '0' && makeNumber.isEmpty == true)  inputAdd = false;
		
		if(inputAdd == true) makeNumber += st ;

		displayNumber = makeNumber ;

		if(displayNumber.length < 7) displayFontSize = 80.0 ;  // 글자 크기를 선택 
		else displayFontSize = 50 ;

		_disignPage.setState(() { displayNumber ;});  // 화면을 갱신한다. 
	}
}
//-----------------------------------------------------------------------------------------

 

 

2. 연산자 키 입력 : 4개의 연산키를 눌렀을 때 적용되는 함수 입니다.

 

void _operatorOnPressed(String st)
{
	_resultOnPressed('=') ;

	selectedOperator = st ;
	firstNumber = double.parse(displayNumber) ;

	makeNumber = '';
	pointExist = false ;
}
//-----------------------------------------------------------------------------------------

 

3. '=', 'C'  연산결과와 클리어 기능의 함수 입니다. 

void _resultOnPressed(String st)
{
	if(st == 'C')		// clear input
	{
		makeNumber = ''; // clear clicked 
		displayNumber = '0' ;
		selectedOperator = '+' ;
	}
	else
	{
		secondNumber = double.parse(makeNumber) ;
		makeNumber = '';

		var result = 0.0 ;

		switch(selectedOperator)
		{
			case '+' : result = firstNumber + secondNumber ;	break ;
			case '−' : result = firstNumber - secondNumber ;	break ;
			case '×' : result = firstNumber * secondNumber ;	break ;
			case '÷' : result = firstNumber / secondNumber ;	break ;
		}

		displayNumber = result.toString();
	}
	 pointExist = false ;

	_disignPage.setState(() { displayNumber ;});  // 화면을 갱신한다. 
}

 

위의 3개 함수를 기존의 Layout 코드의 마지막 부분에 추가하여 넣습니다. 그리고 지난번에 CalButton 위젯 클래스를 아래와 같이 수정합니다. 

class CalButton extends StatelessWidget
{
	CalButton({super.key, required this.caption, required this.color, required this.buttonKind});
	final String caption ;  // 립력된 버튼의 문자 
	final Color color;      // 버튼의 색
	final int buttonKind;   // 입력된 버튼의 기능(0:숫자, 1:연산, 2:기능)

	@override
	Widget build(BuildContext context)
	{
		return ElevatedButton
		(
			onPressed: () 
			{ 
				switch(buttonKind)
				{
					case 0 : _numberOnPressed(caption); break ;
					case 1 : _operatorOnPressed(caption); break ;
					case 2 : _resultOnPressed(caption); break ; 
				}
			},
			style: ElevatedButton.styleFrom
			(
				backgroundColor: color, 
				fixedSize: Size((MediaQuery.of(context).size.width/4)-30, (MediaQuery.of(context).size.width/4)-20), 
				shape: const CircleBorder(),
			),
			child: Text('$caption', style: TextStyle(fontSize: 40,),),
		);
	}
}
//-----------------------------------------------------------------------------------------

마지막으로 MainPage 클래스도 아래 처럼 수정을 합니다. 

class MainPage extends StatefulWidget 
{
	@override
  	DesignPage createState() => _disignPage ; // DesignPage();
}
//-----------------------------------------------------------------------------------------

 

이것으로 계산기 앱의 기본 연산기능이 마무리 됩니다.

Flutter 시작하고 3일만에 만든 앱이라 두서없이 만들었습니다. 조금씩 공부하다 보니 손봐야 할 코드가 꽤 많네요..

제목 그대로 초보자 관점에서 만든것임을 감안해 주세요.. 아래 소스 전체 코드를 파일로 올려 놓습니다. 참고하세요

 

main.dart
0.01MB

반응형
반응형

이번 포스팅은 계산기 Layout 3번째로 키보드를 만들어 봅니다. 기본 화면설계 Layout은 이것으로 마무리가 될듯 합니다. 

한가지 이번 계산기 앱을 플로터 공부하고  2틀 만에 공부해가며 만들고 나서 계속 공부하는 와중에 참 설계가 잘못된 부분이 많구나 하는 것을 알았습니다. 기본적으로 윈도우 프로그램 개발하는 방식과는 차이가 좀 나네요.. 

 

제목에 초보자라는 타이틀이 있으니 초보자 입장에서 설계는 잘못 되었지만 일단은 기존에 설계된 코드로 포스팅을 하고 좀더 공부하면서 설계에 오류나 미진한 부분은 추가로 포스팅 하도록 하겠습니다. 

 

1. 버튼의 모양을 만들기 위한 위젯 Class 구현 

  * 버튼을 하나하나 개별적으로 만들수도 있겠지만 그러면 코드가 길어지기 때문에 클래스로 버튼의 틀을 만들고 속성값만 주어서 버튼이 생성될 수 있도록 합니다. 

   * 버튼 Class

class CalButton extends StatelessWidget
{
	CalButton({super.key, required this.caption, required this.color, required this.buttonKind});
	final String caption ;  // 버튼에 들어갈 문자 
	final Color color;      // 버튼의 색
	final int buttonKind;   // 입력된 버튼의 기능(0:숫자, 1:연산, 2:기능)

	@override
	Widget build(BuildContext context)
	{
		return ElevatedButton
		(
			onPressed: () {},  // 버튼입력 이벤트 처리 부분(나중에 코드 추가)
			style: ElevatedButton.styleFrom
			(
				backgroundColor: color, 
				fixedSize: Size((MediaQuery.of(context).size.width/4)-30, (MediaQuery.of(context).size.width/4)-20), 
				shape: const CircleBorder(),   // 버튼 모양은 원형 
			),
			child: Text('$caption', style: TextStyle(fontSize: 40,),),  // 버튼의 들어가는 글자 모양
		);
	}
}

2. 버튼 그룹 위젯 생성 

  * 위에 만들어진 버튼을 이용하여 4x5 행렬로 키보드 위젯을 만듭니다.

  * 위젯은 Table 라는 layout 위젯을 사용합니다. 

  * Class 구현 

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

  	@override
  	Widget build(BuildContext context) 
	{
    	return Table
		(
			border: TableBorder.all(),
      		columnWidths: const <int, TableColumnWidth>
			{
        		0: FlexColumnWidth(),
        		1: FlexColumnWidth(),
        		2: FlexColumnWidth(),
				3: FlexColumnWidth(),  
      		},
      		defaultVerticalAlignment: TableCellVerticalAlignment.middle,	
      		children: <TableRow>
			[
        		TableRow
				(
					decoration: const BoxDecoration(color: Colors.black,),
          			children: <Widget>
					[
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: 'C', color: Colors.grey, buttonKind: 2,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '+/-', color: Colors.grey, buttonKind: 1,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '%', color: Colors.grey, buttonKind: 1,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '÷', color: Colors.orange, buttonKind: 1,),),
          			],
        		),
        		TableRow
				(
					decoration: const BoxDecoration(color: Colors.black,),  
          			children: <Widget>
					[
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '7', color: Color.fromARGB(255, 61, 61, 61), buttonKind: 0,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '8', color: Color.fromARGB(255, 61, 61, 61), buttonKind: 0,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '9', color: Color.fromARGB(255, 61, 61, 61), buttonKind: 0,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '×', color: Colors.orange, buttonKind: 1,),),
          			],
        		),
				TableRow
				(
					decoration: const BoxDecoration(color: Colors.black,),
          			children: <Widget>
					[
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '4', color: Color.fromARGB(255, 61, 61, 61), buttonKind: 0,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '5', color: Color.fromARGB(255, 61, 61, 61), buttonKind: 0,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '6', color: Color.fromARGB(255, 61, 61, 61), buttonKind: 0,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '−', color: Colors.orange,buttonKind: 1,),),
          			],
        		),
				TableRow
				(
					decoration: const BoxDecoration(color: Colors.black,),
          			children: <Widget>
					[
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '1', color: Color.fromARGB(255, 61, 61, 61), buttonKind: 0,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '2', color: Color.fromARGB(255, 61, 61, 61), buttonKind: 0,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '3', color: Color.fromARGB(255, 61, 61, 61), buttonKind: 0,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '+', color: Colors.orange, buttonKind: 1,),),
          			],
        		),			
				TableRow
				(
					decoration: const BoxDecoration(color: Colors.black,),
          			children: <Widget>
					[
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '0', color: Color.fromARGB(255, 61, 61, 61), buttonKind: 0,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '2', color: Color.fromARGB(255, 61, 61, 61), buttonKind: 1,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '.', color: Color.fromARGB(255, 61, 61, 61), buttonKind: 0,),),
						Padding(padding: EdgeInsets.all(5), child: CalButton(caption: '=', color: Colors.orange, buttonKind: 2,),),
          			],
        		),		
      		],
    	);
  	}
}

3. 이제 만들어진 버튼 그룹위젯을 화면에 추가 합니다. 

  * 지난 포스팅에 만든 DesignPage 클래스에 그룹 위젯을 생성 하여 넣습니다. 

  * 아래 코드에서 추가 되는 부분을 넣으시면 됩니다. 

class DesignPage extends State<MainPage>  
{
  	@override
	Widget build(BuildContext context)
	{
		return Scaffold
		(
			appBar: AppBar(title: Text('Calculator Program'),),
			body: Column
			(
				crossAxisAlignment: CrossAxisAlignment.stretch,
				children: <Widget>
				[
					Container
					(
						padding: EdgeInsets.all(30),
						alignment: Alignment(1.0, 1.0),   
						color: Colors.black,    					
						height: (MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top) * 0.30,   // 화면의 30%를 차지하도록 설정
						child: displayText(caption: '$displayNumber', fontsize: displayFontSize,),
					),
          			ButtonGroupWidget(),  // 추가코드(버튼그룹위젯 생성)
				],					
			),
			backgroundColor: Colors.black,   
		);
	}
}

이것으로 화면 디자인에 대한 layout은 마무리 되었습니다. 

지금까지 만들어진 코드를 실행해 보면 처음에 보여 드린 계산기 모양이 나타나게 될 겁니다. 

반응형
반응형

이번장에는 계산기의 숫자판에 대한 Layout를 코딩해 보도록 하겠습니다. 

숫자판은 Text 위젯을 사용할 것이고 이 Text 위젯은 Container 라는 위젯을 담기위한 위젯안에 들어가게 됩니다. 

Container 위젯은 내부에 또 다른 위젯을 담기위해 사용하고 위젯은 1개만 넣을 수 있습니다. 

 

1. Container 위젯 코드 

Container
(
    padding: EdgeInsets.all(30),  
    alignment: Alignment(1.0, 1.0),  
    color: Colors.black,
    height: (MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top) * 0.30,
    child: displayText(caption: '$displayNumber', fontsize: displayFontSize,),       
),

  (1) 코드 설명  

    * padding: EdgeInsets.all(30),  : Container 내부 모든면에 30 의 값으로 빈 공간을 확보 합니다. 

Container

    * alignment: Alignment(1.0, 1.0),   : 내부 위젯의 위치를 우측 하단으로 설정 

    * color: Colors.black, : 배경색을 검정으로 

    * height: (MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top) * 0.30, :

       Container의 높이를 전체화면의 30% 로 설정합니다. 높이 설정은 핸드폰 마다 화면사이즈가

       틀리기 때문에 핸드폰의 세로값을 불러와서 세로값의 30% 의 크기로 자동 생성됩니다.

    * child: displayText(caption: '$displayNumber', fontsize: displayFontSize,), : Text 위젯을 생성하고 Container에 넣습니다. 

 

  (2) Text 위젯 Class 코드 

class displayText extends StatelessWidget
{
	displayText({super.key, required this.caption, required this.fontsize});
	final String caption ;
	final double fontsize ;

	@override
	Widget build(BuildContext context)
	{
		return Text
		(
			'$caption', 
			style: TextStyle(color: Colors.white, backgroundColor: Colors.black, fontSize: fontsize,),
			textAlign: TextAlign.right,
		) ;
	}
}

  * Container 내부에 들어가는 Text 위젯에 대한 코드입니다. 

    - 생성자의 파라메터로 Text에 들어갈 Caption 과 Text의 폰트 크기값을 받습니다. 

 

2. DesignPage 클래스 내용 수정 : 지난 포스팅에서 일부 작성된 DesignPage 클래스에 Container 를 추가 합니다. 

class DesignPage extends State<MainPage>  
{
  	@override
	Widget build(BuildContext context)
	{
		return Scaffold
		(
			appBar: AppBar(title: Text('Calculator Program'),),
			body: Column
			(
				crossAxisAlignment: CrossAxisAlignment.stretch,
				children: <Widget>
				[
					Container    // 추가된 코드
					(
						padding: EdgeInsets.all(30),
						alignment: Alignment(1.0, 1.0),   // 내부 위젯의 위치를 우측 하단으로 설정 
						color: Colors.black,
                        height: (MediaQuery.of(context).size.height - MediaQuery.of(context).padding.top) * 0.30,   // 화면의 30%를 차지하도록 설정
    					child: displayText(caption: '$displayNumber', fontsize: displayFontSize,),
					),
				],					
			),
			backgroundColor: Colors.black,   
		);
	}
}
반응형
반응형

앱설계 첫번째로 화면 Layout 을 설계 합니다. 

Flutter 는 모든 구성요소들이 Widget 이라는 모듈로 되어 있고 이 모듈들을 화면에 어떻게 배치할 것인가를 설계하는 것에서 프로그램이 시작 됩니다. 어떤 위젯을 사용할 것인지 어떻게 배치할 것인지에 대한 Layout을 먼저 구성 합니다. 

 

1. Layout 설계 : 계산기 앱을 만들기에 앞서 아래 그림과 같이 기본 Layout을 설계 했습니다. 아래 그림에서 블럭 하나하나가 다 Widget 입니다. 물론 더 세세하게 내려가면서 위젯이 더 있지만 앱 설계의 큰 틀은 아래와 같습니다. 

계산기 앱 Layout
기본 layout

2. 화면 Layout 코딩 설계

  C# 이나 visual basic 처럼 예전에는 화면을 디자인 하기위해 별도의 디자인 에디터에서 설계를 했었는데 최근 개발툴의 추세는 코딩으로 UI 설계를 하는 방향으로 변화하는 듯 합니다. 

 

  (1) 프로그램 진입 ( main 함수 ) : Dart 언어도 C 언어처럼 프로그램의 시작을 main 함수에서 시작 합니다. 

    * CalculatorApp() 이라는 위젯을 생성하는 것으로 프로그램이 시작 됩니다.

    * CalculatorApp 은 MaterialApp 을넘겨 받도록 위젯 Class 로 설계 되어 있다. 

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

void main() 
{
	runApp(const CalculatorApp());
}
//-----------------------------------------------------------------------------------------

  (2) MaterialApp :

    * Material Design Pattern 으로 설계가 진행 되도록 위젯들이 구성되어 진다. 

    * 앱 설계 디자인을 선택하기 때문에 앱의 가장 근본(root) 로 설정이 이루어 진다. 

    * 기본 구조 

           - title : 간단한 앱의 한 줄 설명입니다. ( 특별한 기능은 없음)

           - theme : 앱의 테마를 지정 

           - home : MaterialApp의 기본 경로로 앱 실행 시 가장 먼저 볼 수 있는 화면(Scaffold 위젯을 사용하여 정의)

    * MaterialApp의 클래스 구조는 아래와 같다.  

    * 여기까지 코드 작성이 되었다면 그림을 그리기 위한 도화지는 준비가 된 것입니다. 

class CalculatorApp extends StatelessWidget 
{
	const CalculatorApp({super.key});
  
	@override
	Widget build(BuildContext context)  // This widget is the root of your application.
	{
		return MaterialApp
		(
			title: 'Calculator App',
			theme: ThemeData(primarySwatch: Colors.blue,),
			home: MainPage(),   // 실제 화면UI가 구현되는 widget(Scaffold)
		);
	}
}

  (3) Scaffold : 본격적으로 그림을 그리기에 앞서 도화지의 영역을 나누어 봅니다. 

    * MaterialApp 의 하위 개념으로 home 에서 호출이 되어 화면에 출력이 된다. 

    * 기본 구조 : 기본구조는 크게 3 영역으로 나뉘며 Body를 제외하고 나머지는 생략이 가능하다. 

      - AppBar : 주로 창 이름을 담당하는 영역 설정 

      - Body : 앱 동작의 주요 영역 설정 

      - BottomNavigationBar : 다른 창 으로 이동할 수 있는 버튼들이 있는 영역 설정 

      - 기타 위젯 설정 ( 예 : FloatingActionButton : 창 위에 떠 있는 효과를 주는 버튼) 

    * Scaffold 의 클래스 구조는 아래와 같다.  

class MainPage extends StatefulWidget 
{
	@override
  	DesignPage createState() => DesignPage();
}
//-----------------------------------------------------------------------------------------
class DesignPage extends State<MainPage>  
{
  	@override
	Widget build(BuildContext context)
	{
		return Scaffold
		(
			appBar: AppBar(title: Text('Calculator Program'),),    // 앱의 제목이 표시되는 영역설정
			body: Column    // 앱의 주요 화면UI 영역 설정 (Column 위젯이 사용됨) 
			(
				crossAxisAlignment: CrossAxisAlignment.stretch,
				children: <Widget>
				[
				],					
			),
			backgroundColor: Colors.black,   // 화면의 전체 배경색을 지정
		);
	}
}

 

여기까지 코딩으로 도화지를 준비하고 도화지에 그림그릴 영역을 나누고 제목을 적어 주는것까지 완료가 되었습니다. 

반응형
반응형

* 앱 개발 공부를 해 봐야지 하고 여러 개발 플랫폼과 개발 언어를 봐오던 차에 Flutter 를 접하고 괜찮겠다 라는 생각이 들어 본격적으로 공부를 시작했습니다. 

 

* 저처럼 처음 공부하는 분들 입장에서 최대한 순차적으로 이해해야 할 부분에 대해 정리하는 개념으로 포스팅을 할 예정입니다. 

* 개발언어에 완전 초보이신 분들은 조금 어려울 수도 있겠습니다.

 

1. Flutter 를 선택한 이유는

  (1) C++ 과 C# 언어에 익숙해서 Dart 언어를 쉽게 이해하고 접할 수 있는 부분

  (2) Multi Flaform 개발이 가능하다.

  (3) 한글로 된 도서가 있더라..( 다른 개발툴들은 거의 없는 한글 책이 있어서 구매 했는데 돈이 아까워서 시작 )

  (4) 웹에서 필요한 정보를 얻기 쉽다. 

 

일단 시작 개발툴은 정했고 Dart 라는 생소한 언어를 책을 통해 훝어 보았습니다. 한 2일 정보 보니 C#과 비슷하네요.. 몇몇 상용구나 Dart 만의 문법 몇가지 보니 바로 시작할 수 있겠다 싶어 앱을 하나 만들면서 시작하고자 가장 간단한 계산기를 하나 만들어 보는 것으로 시작했습니다.

2. 계산기 UI 선택 

  (1) 계산기의 UI를 어떻게 할까 생각하다가 일단 공부이니 내 아이폰에 있는 계산기를 레퍼런스로 정했습니다. 

  (2) 일단 공부하는 것이니 만큼 완벽하지는 않겠지만 최대한 비슷하게 해 보려고 했습니다.  

  (3) 설계된 UI 는 아래와 같습니다. 

계산기 UI
왼쪽이 아이폰계산기 오른쪽이 제가 만든 계산기입니다.

 

반응형

+ Recent posts