Flutter

두번째 Flutter App - 환율 계산기 만들기 2

joeyRun 2022. 9. 21. 19:16
반응형

3. Main UI 구현

 

기본적인  환율 정보 가져오기까지는 이전 포스팅에서 완료했으니, 이제 화면을 꾸며보겠습니다.

 

우선, 기본적으로 만들어져있는 Main UI를 살짝 수정합니다.

간단히는 기본으로 만들어지는 MyHomePage 와 _MyHomePageState class들을 삭제하고,

Scaffold를 새롭게 꾸며주려고합니다. 아래와 같이 뒤쪽 코드를 전부 지워주고, MyApp class만 남깁니다.

import 'package:exchange_rate_calculator/exchange_rate.dart';
import 'package:flutter/material.dart';

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

이제 MyHomePage.dart라는 새로운 파일을 만들고, 코딩을 해보겠습니다.

빈 파일에서 stf 를 치시면 Stateful Widget 코드 제안이 뜰겁니다. 선택하고, 이름을 MyHomePage로 해줍시다.

MyHomePage class

StatefulWidget인 MyHomePage는 MyHomePage class와 _MyHomePageState class 2개로 이루어져있습니다.

MyHomePage class가 Widget의 본체이고,

State 의 변화에 따라 _MyHomePageState의 build 함수가 return하는 Widget을 화면에 뿌려주게됩니다.

 

Flutter/Dart에서는 하나의 main 함수가 모든 코드의 시작점이고,

우리의 main 함수에서는 아래와 같이 MyApp 이라는 App Widget을 실행토록 되어있습니다.

(편의를 위해 comments를 삭제했습니다.)

 

MyApp은 title, theme을 정하고, 화면에 뿌려질 home으로 다시 MyHomePage Widget을 불러오게됩니다.

한가지 봐야할 점은 parameter로 title: 'Flutter Demo Home Page' 를 지정하고 있는 점입니다.

import 'package:flutter/material.dart';
import 'MyHomePage.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(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

하지만, 현재 MyHomePage class는 title 이라는 parameter를 가지고 있지않습니다.

그래서, class 생성자에서 title을 받을 수 있도록 아래와 같이 수정해봤습니다.

간단히 title이라는 변수를 만들고, 생성자에서 해당 변수를 받아서 할당하는 방식입니다.

Dart 언어에서는 java와 같이 Constructor에서 변수를 할당하는 코드를 하나하나 구현하지않아도됩니다.

this.title과 같이 받아야할 변수명에 this를 넣어주면 class 변수인 title에 자동 할당해줍니다.

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

 

_MyHomePageState class

Main UI 구현의 핵심이 되는 부분입니다.

기본 UI 및 사용자의 입력에 따라 화면의 변화까지 이 class에서 구현해야합니다.

build 함수에서 return하는 widget이 화면에 뿌려지는 모습을 구성합니다.

 

 

먼저  몇가지 _MyHomePageState class 내에서 사용할 변수들을 지정해줍니다.

class _MyHomePageState extends State<MyHomePage> {
  late Future<List> _rates;
  String _selectedCurrency = ExchangeRate.currencyKorea;
  double _selectedCurrencyValue = 0.0;
  double _money = 0.0;
}

_rates는 web page로부터 읽어올 환율 정보입니다.

해당 정보는 읽어오는데 시간이 걸리므로 Future를 이용해 별도 thread로 동작하도록하고 있습니다.

 

_selectedCurrency는 현재 선택된 기준 환율 국가입니다. 해당 국가의 화폐 단위로 타국가 화폐 단위를 환산하겠습니다.

 

_selectedCurrencyValues는 현재 선택된 기준 환율 국가의 환율입니다.

 

_money는 입력창에 보이는 사용자가 입력한 금액입니다. 사용자 입력에 따라 변화합니다.

 

 

이제 _MyHomePageState class가 처음 시작할때 먼저 해야하는 일들을 구현해줍니다.

class _MyHomePageState extends State<MyHomePage> {
  late Future<List> _rates;
  String _selectedCurrency = ExchangeRate.currencyKorea;
  double _selectedCurrencyValue = 0.0;
  double _money = 0.0;

  @override
  void initState() {
    super.initState();
    _rates = ExchangeRate.readRate();
  }
}

방법은 간단합니다. initState 함수를 override하면되고, super에서 정의된 super.initState()를 실행해준 후,

환율 정보를 web page에서 읽어오도록 해두었습니다.

 

이제 실제 화면에 표시될 widget 들을 build 함수의 return값에 넣어주겠습니다.

build함수의 모습은 아래와 같습니다.

@override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: FutureBuilder(
          future: _rates,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator();
            }
            if (snapshot.hasData) {
              final rates = snapshot.data as List<Map<String, double>>;
              _selectedCurrencyValue = _getSelectedCurrencyValue(rates);
              return Center(
                child: Column(
                  children: [
                    Expanded(
                      child: _getListWidget(rates),
                    ),
                    _getUpdateButton(),
                    _getInputWidget(rates),
                  ],
                ),
              );
            } else {
              return const CircularProgressIndicator();
            }
          },
        ),
      ),
    );
  }

① 우선, Home 화면의 기본틀을 잡아주기 위해, Scaffold가 최상위에 있습니다.

    여기서는 Scaffold의 멤버변수들 중 appBar 와 body 만 사용해서 구현했고,

    화면에는 상단부에 appBar가 보이고, 아래쪽 전체화면에는 body에 정의된 Widget 들이 들어갑니다.

 

② appBar 에는 AppBar Widget을 넣어줍니다.

    AppBar Widget속에는 Text로 MyApp class에서 MyHomePage를 call할 시에 전달했던 title을 찍어주도록했습니다. 기억해두어야할 점은 MyHomePage class의 변수를 _MyHomePageState에서 참조해야 할 경우 widget.변수명 의 형태로 참조 해야한다는 접입니다. 실제 StatefulWidget들을 불러올 경우 부르는 쪽에서 변수를 전달해야할 경우가 많이 발생합니다. 그리고 실제로 그 변수들을 _MyHomePageState와 같은 State class에서 사용하는 경우가 대다수입니다. 이런 경우 widget.변수명 형태로 참조할 수 있으니 기억해주세요.

 

③ body의 최상위는 Center 입니다. 해당 위젯은 child에 해당하는 위젯을 화면의 중앙에 위치시켜줍니다.

    일반적으로 body에는 Center나 Column, Row 와 같은 위치 정렬 위젯을 쓰는 경우가 많습니다.

 

④ Center의 child로 FutureBuilder 위젯을 사용했습니다.

    해당 위젯은 Future type의 변수들을 화면에서 활용할 때 자주 사용합니다. 예를 들어 업데이트에 10초가 걸린다고하면, 10초 동안 화면에 정보를 뿌려줄 수 가 없으니, FutureBuilder를 이용해서 해당 시간동안 prgressbar등을 보여주면서 사용자에게 기다리도록 하게됩니다. FutureBuilder의 멤버변수로 future에는 나중에 업데이트될 Future type의 변수명을 전달하면되고, builder에서는 Future type 변수의 snapshot을 확인해서, 아직 waiting 상태이거나, update는 완료되었으나 data가 없거나 Error가 발생했다면 CircularProgressIndicator로 화면에 뱅글뱅글 돌아가는 UI를 뿌려줍니다. snapshot이 update가 완료된 상태라면 제대로된 화면을 뿌려줍니다.

 

이제 나머지 함수들을 구현해줍니다.

  _getSelectedCurrencyValue(), _getListWidget(), _getUpdateButton(), _getInputWidget() 총 4 종입니다.

 

_getSelectedCurrencyValue()

  double _getSelectedCurrencyValue(List<Map> rates) {
    for (var e in rates) {
      if (e.containsKey(_selectedCurrency)) {
        return e.values.first;
      }
    }
    return -1.0;
  }

환율 정보는 언제 읽어올 수 있을지 모르는 상태입니다. 따라서 FutureBuilder를 통해 업데이트가 완료되는 시점에 해당 정보를 통해 현재 선택된 국가의 환율을 읽어와야합니다. class내 멤버 변수인 _selectedCurrency를 통해 환율 Map 리스트 전체를 뒤져서 환율값을 반환합니다. 실패할 경우 -1을 반환합니다.

 

_getListWidget()

앞서 언급했던 것처럼, 각 국가별 통화기호를 사용해서 표시해주고 싶습니다.

이를 위해서는 먼저 intl 패키지 설치를 해야합니다.

intl 패키지는 Locale 정보를 활용해서 각국의 통화, 숫자, 시간 표시 정보 등을 간편하게 다룰 수 있게 도와줍니다.

https://pub.dev/packages/intl

 

intl | Dart Package

Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.

pub.dev

 

VS Code의 Editing 창에서 Ctrl + ` 를 입력해서 terminal을 열고,

flutter pub add intl 을 입력해줍니다.

실제 코드는 아래와 같습니다.

  ReorderableListView _getListWidget(List<Map<String, double>> rates) {
    return ReorderableListView.builder(
      itemCount: rates.length,
      itemBuilder: (context, index) {
        final formatCurrency = NumberFormat.simpleCurrency(
            decimalDigits: 2,
            locale: ExchangeRate.currencyLocale[rates[index].keys.first]);
        return Padding(
          key: Key('$index'),
          padding: const EdgeInsets.all(4.0),
          child: ListTile(
            tileColor: Colors.grey.shade100,
            title: Row(
              children: [
                Text(rates[index].keys.first),
                Expanded(
                  flex: 10,
                  child: Align(
                    alignment: Alignment.centerRight,
                    child: Text(formatCurrency.format(_money /
                        _selectedCurrencyValue *
                        rates[index].values.first)),
                  ),
                ),
                const Spacer(flex: 1),
              ],
            ),
          ),
        );
      },
      onReorder: (int oldIndex, int newIndex) {
        setState(() {
          if (newIndex > oldIndex) {
            newIndex = newIndex - 1;
          }
          final rate = rates.removeAt(oldIndex);
          rates.insert(newIndex, rate);
        });
      },
    );
  }

입력된 금액의 각국 환율 변환값들은 List 형태로 보여주려고합니다. 국가가 워낙 많아서, 사용자가 본인이 원하는데로 순서를 변경할 수 있었으면하므로, ReorderableListView를 썼습니다. ReorderableListView는 3개의 멤버 변수가 필요합니다.

 

itemCount는 전체 item의 개수입니다.

 

itemBuilder는 list 의 각 한 아이템을 표현하는 Widget입니다.

 

onReorder는 일반 ListVIew에는 존재하지않습니다. Reordering을 하는 경우에만 필요하니까요.

list 상의 아이템 순서가 변경되면, rates 변수의 순서도 같이 변경해줍니다. rates 변수가 변화하는 경우 화면상의 list도 업데이트되어야하므로 setState() 안에 넣어줍니다.

 

앞서 설치했던 intl 패키지는 itemBuilder 속에서 사용됩니다.

아래 코드와 같이 NumberFormat.simpleCurency로 소숫점 이하 2자리 및 국가별 locale을 세팅해주고,

list의 각 아이템 안에는 사용자가 입력한 _money값에 해당하는 국별 변환 금액을 국가별 locale에 맞게 포맷팅해서 뿌려줍니다.

    final formatCurrency = NumberFormat.simpleCurrency(
        decimalDigits: 2,
        locale: ExchangeRate.currencyLocale[rates[index].keys.first]);
            
    Text(formatCurrency.format(_money /
        _selectedCurrencyValue * rates[index].values.first)),

 

_getUpdateButton()

  ElevatedButton _getUpdateButton() {
    return ElevatedButton(
      onPressed: () {
        setState(() {
          _rates = ExchangeRate.readRate();
        });
      },
      child: const Text('Update currency rate...'),
    );
  }

상대적으로 간단하네요. 사용자가 버튼을 클릭하면 환율을 업데이트해줍니다.

 

_getInputWidget()

사용자 입력을 받는 Widget들입니다.

가로로 배열하기 위해 Row를 최상위에 사용해주고,

children으로, 2개의 Widget을 두었습니다.

 

우선 DropdownButton으로 기준 환율 국가를 선택할 수 있게해줍니다.

추가로 금액을 입력할 수 있는 TextFormField를 제공합니다.

 

직관적으로 하나씩 보시면 이해할 수 있을 듯하여 자세한 설명은 Skip하겠습니다.

Dart 언어와 관련된 List, Map 등은 나중에 별도로 한 번 다루겠습니다.

  Row _getInputWidget(List<Map<String, double>> rates) {
    return Row(
      children: [
        DropdownButton(
          underline: Container(),
          value: _selectedCurrency,
          items: rates
              .map((e) => DropdownMenuItem(
                  value: e.keys.first, child: Text(e.keys.first)))
              .toList(),
          onChanged: (value) {
            setState(() {
              _selectedCurrency = value ?? '';
              _selectedCurrencyValue = _getSelectedCurrencyValue(rates);
            });
          },
        ),
        Expanded(
          child: TextFormField(
              maxLines: 1,
              keyboardType: TextInputType.number,
              inputFormatters: [
                FilteringTextInputFormatter.allow(RegExp(r'[\d\.]'))
              ],
              textAlign: TextAlign.right,
              onChanged: (value) {
                setState(() {
                  _money = double.parse(value);
                });
              }),
        ),
      ],
    );
  }

 

 

 

 

전체 코드는 아래와 같습니다.

더보기
import 'package:exchange_rate_calculator/exchange_rate.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late Future<List> _rates;
  String _selectedCurrency = ExchangeRate.currencyKorea;
  double _selectedCurrencyValue = 0.0;
  double _money = 0.0;

  @override
  void initState() {
    super.initState();
    _rates = ExchangeRate.readRate();
  }

  double _getSelectedCurrencyValue(List<Map> rates) {
    for (var e in rates) {
      if (e.containsKey(_selectedCurrency)) {
        return e.values.first;
      }
    }
    return -1.0;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: FutureBuilder(
          future: _rates,
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const CircularProgressIndicator();
            }
            if (snapshot.hasData) {
              final rates = snapshot.data as List<Map<String, double>>;
              _selectedCurrencyValue = _getSelectedCurrencyValue(rates);
              return Center(
                child: Column(
                  children: [
                    Expanded(
                      child: _getListWidget(rates),
                    ),
                    _getUpdateButton(),
                    _getInputWidget(rates),
                  ],
                ),
              );
            } else {
              return const CircularProgressIndicator();
            }
          },
        ),
      ),
    );
  }

  ReorderableListView _getListWidget(List<Map<String, double>> rates) {
    return ReorderableListView.builder(
      itemCount: rates.length,
      itemBuilder: (context, index) {
        final formatCurrency = NumberFormat.simpleCurrency(
            decimalDigits: 2,
            locale: ExchangeRate.currencyLocale[rates[index].keys.first]);
        return Padding(
          key: Key('$index'),
          padding: const EdgeInsets.all(4.0),
          child: ListTile(
            tileColor: Colors.grey.shade100,
            title: Row(
              children: [
                Text(rates[index].keys.first),
                Expanded(
                  flex: 10,
                  child: Align(
                    alignment: Alignment.centerRight,
                    child: Text(formatCurrency.format(_money /
                        _selectedCurrencyValue *
                        rates[index].values.first)),
                  ),
                ),
                const Spacer(flex: 1),
              ],
            ),
          ),
        );
      },
      onReorder: (int oldIndex, int newIndex) {
        setState(() {
          if (newIndex > oldIndex) {
            newIndex = newIndex - 1;
          }
          final rate = rates.removeAt(oldIndex);
          rates.insert(newIndex, rate);
        });
      },
    );
  }

  ElevatedButton _getUpdateButton() {
    return ElevatedButton(
      onPressed: () {
        setState(() {
          _rates = ExchangeRate.readRate();
        });
      },
      child: const Text('Update currency rate...'),
    );
  }

  Row _getInputWidget(List<Map<String, double>> rates) {
    return Row(
      children: [
        DropdownButton(
          underline: Container(),
          value: _selectedCurrency,
          items: rates
              .map((e) => DropdownMenuItem(
                  value: e.keys.first, child: Text(e.keys.first)))
              .toList(),
          onChanged: (value) {
            setState(() {
              _selectedCurrency = value ?? '';
              _selectedCurrencyValue = _getSelectedCurrencyValue(rates);
            });
          },
        ),
        Expanded(
          child: TextFormField(
              maxLines: 1,
              keyboardType: TextInputType.number,
              inputFormatters: [
                FilteringTextInputFormatter.allow(RegExp(r'[\d\.]'))
              ],
              textAlign: TextAlign.right,
              onChanged: (value) {
                setState(() {
                  _money = double.parse(value);
                });
              }),
        ),
      ],
    );
  }
}

 

 

실행해보면 아래와 같습니다.

2가지가 맘에 걸리네요.

우선, 현재 기본 입력값이 0으로 세팅되어있어서 전부 값이 0으로 표시됩니다. 사실 오류는 아닙니다만, 조금 이상해보이네요.

또 한가지, AppBar 부분에 제목이 영 맘에 안드네요.  

전반적으로 좀 손을 좀 봐야겠습니다.

 

4. 설정 UI 구현

환율 계산기 설정 UI를 구현해봅니다.

 

우선은 AppBar 위젯에서 메뉴 아이템들을 설정해주고, 각각의 메뉴 아이템별로 실행 코드를 작성해주면 됩니다.

참 쉽죠?

 

먼저 AppBar 위젯을 만들어 봅니다.

 

우선 Language 세팅별로 AppBar에 표시될 언어를 변경해주기위해서 strings.dart 파일에 언어별로 title 들을 구현 해두었습니다. 해당 Map을 참조해서, 언어세팅이 변경되면 표시 언어도 변경되게 해주려고합니다.

strings.dart

import 'package:exchange_rate_calculator/settings.dart';

const String sharedPreferencesExchangeRateKey = 'data';
const String sharedPreferencesSettingsKey = 'settings';
// Site providing Exchange rate : https://github.com/fawazahmed0/currency-api
const String webPageCurrencies =
    'https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies.min.json';
const String webPageRatesBasedOnBTC =
    'https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies/btc.json';
const String webKeyDate = 'date';
const String webKeyBTC = 'btc';

enum Titles {
  title,
  updateCurrencyRates,
  filters,
  language,
  resetAllSettings,
  about,
  updateButton,
}

const Map titles = {
  Language.ENGLISH: {
    Titles.title: 'Exchange Rate Calculator',
    Titles.updateCurrencyRates: '  Update currency rates',
    Titles.filters: '  Filters',
    Titles.language: '  Language',
    Titles.resetAllSettings: '  Reset all settings',
    Titles.about: '  About',
    Titles.updateButton: 'Updated in'
  },
  Language.KOREAN: {
    Titles.title: '환율 계산기',
    Titles.updateCurrencyRates: '  환율 정보 업데이트',
    Titles.filters: '  필터',
    Titles.language: '  언어',
    Titles.resetAllSettings: '  세팅 초기화',
    Titles.about: '  About',
    Titles.updateButton: '업데이트'
  },
};

 

 

다음은 실제 AppBar의 구현입니다.

기본은 AppBar 위젯으로 구현하면 됩니다만, 앞에서 얘기한 것처럼 언어 세팅별로 표시 언어를 변경해주기위해 Provider / Consumer 패키지를 사용했습니다. Flutter에서 Provider는 데이터와 UI를 분리하고, 데이터의 변화가 생기면 UI를 업데이트 해 줄 수 있는 구조를 제공합니다. 나중에 별도의 포스팅을 준비해보겠습니다.

 

AppBar는 사실 Scaffold 위젯속에 바로 구현해도됩니다만, 너무 복잡해지니 별도의 파일로 분리해보았습니다.

MyAppBar.dart

import 'package:exchange_rate_calculator/data/data_adapter.dart';
import 'package:exchange_rate_calculator/strings.dart';
import 'package:exchange_rate_calculator/ui/MyAbout.dart';
import 'package:exchange_rate_calculator/ui/MyFilters.dart';
import 'package:exchange_rate_calculator/ui/MyLanguageSelect.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'MyHomePage.dart';

class MyAppBar extends StatelessWidget implements PreferredSizeWidget {
  const MyAppBar({
    super.key,
    required this.widget,
  });

  final MyHomePage widget;

  @override
  Widget build(BuildContext context) {
    return Consumer<DataAdapter>(builder: (context, dataAdapter, child) {
      return AppBar(
        title: Text(titles[dataAdapter.language][Titles.title]),
        actions: [
          PopupMenuButton(
            itemBuilder: (context) {
              return [
                PopupMenuItem(
                    value: 0,
                    child: Row(
                      children: [
                        const Icon(Icons.update, color: Colors.blue),
                        Text(titles[dataAdapter.language]
                            [Titles.updateCurrencyRates]),
                      ],
                    )),
                PopupMenuItem(
                    value: 1,
                    child: Row(
                      children: [
                        const Icon(Icons.settings, color: Colors.blue),
                        Text(titles[dataAdapter.language][Titles.filters]),
                      ],
                    )),
                PopupMenuItem(
                    value: 2,
                    child: Row(
                      children: [
                        const Icon(Icons.settings, color: Colors.blue),
                        Text(titles[dataAdapter.language][Titles.language]),
                      ],
                    )),
                PopupMenuItem(
                    value: 3,
                    child: Row(
                      children: [
                        const Icon(Icons.settings, color: Colors.blue),
                        Text(titles[dataAdapter.language]
                            [Titles.resetAllSettings]),
                      ],
                    )),
                PopupMenuItem(
                    value: 1000,
                    child: Row(
                      children: [
                        const Icon(Icons.question_mark, color: Colors.blue),
                        Text(titles[dataAdapter.language][Titles.about]),
                      ],
                    )),
              ];
            },
            onSelected: (value) {
              switch (value) {
                case 0:
                  dataAdapter.readRate();
                  break;
                case 1:
                  Navigator.of(context).push(MaterialPageRoute(
                      builder: ((context) => const MyFilters())));
                  break;
                case 2:
                  showDialog(
                    context: context,
                    builder: (context) => AlertDialog(
                      title: const Text('Select Language...'),
                      content: const MyLanguageSelect(),
                      actions: [
                        ElevatedButton(
                          onPressed: () => Navigator.pop(context),
                          child: const Text('OK'),
                        ),
                      ],
                    ),
                  );
                  break;
                case 3:
                  dataAdapter.resetAllValue();
                  break;
                case 1000:
                  showDialog(
                    context: context,
                    builder: (context) => const AlertDialog(content: MyAbout()),
                  );
                  break;
                default:
                  break;
              }
            },
          )
        ],
      );
    });
  }

  @override
  Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}

 

일반적인 AppBar의 구조는 아래와 같습니다.

AppBar(
        title: Text(' 타이틀 '),
        actions: [
          PopupMenuButton(
            itemBuilder: (context) {
              return [
                const PopupMenuItem(value: 0, child: Text(' 메뉴 0 ')),
                const PopupMenuItem(value: 0, child: Text(' 메뉴 1 ')),
                const PopupMenuItem(value: 0, child: Text(' 메뉴 2 ')),
                const PopupMenuItem(value: 0, child: Text(' 메뉴 3 ')),
                const PopupMenuItem(value: 0, child: Text(' 메뉴 10 ')),
              ];
            },
            onSelected: (value) {
              switch (value) {
                case 0:
                  action0();
                  break;
                case 1:
                  action1();
                  break;
                case 2:
                  action2();
                  break;
                case 3:
                  action3();
                  break;
                case 10:
                  action4();
                  break;
                default:
                  break;
              }
            },
          )
        ],
      );

 

직관적으로 이해할 수 있긴합니다만, 설명하자면 AppBar widget에 전달되는 parameter 들은 다음과 같습니다.

  title : 제목줄에 표시되는 widget,

  action : [ 메뉴 아이템들 ],

  onSelected : 메뉴 아이템의 value별로 수행될 action 지정

 

현재까지의 전체 코드는 GitHub에 올려두었습니다.

https://github.com/joeytestcode/exchange_rate_calculator

 

GitHub - joeytestcode/exchange_rate_calculator

Contribute to joeytestcode/exchange_rate_calculator development by creating an account on GitHub.

github.com

GitHub 사용법은 별도로 포스팅해두었으니 참조하시면 됩니다.

 

https://joeyhwang.tistory.com/15

 

Git : VS code에서 Git 사용해보기

stack overflow와 GitHub는 코딩을 하다보면 정말 많이 가보게되는 사이트입니다. 문제가 잘 해결 안될때 구글링해보면 대다수 이 두 사이트에서 답을 찾게 되는 경우가 많습니다. stack overflow에서는

joeyhwang.tistory.com

 

좋은 하루 되세요~

반응형