Flutter

Flutter 앱 구조/Widget/Singleton

kakaroo 2022. 2. 6. 13:29
반응형

article logo

 

Flutter 앱의 전체 구조는 다음과 같습니다.

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget { }

class MyHomePage extends StatefulWidget { }

class _MyHomePageState extends State<MyHomePage> { }

화면을 그리는 모든 디자인 요소를 위젯이라고 합니다. package:flutter/material.dart 패키지에는 머티리얼 디자인 위젯들이 포함되어 있습니다. 아이폰 디자인의 cupertino 패키지도 제공하므로 필요에 따라 import 해서 사용할 수 있습니다.

import 'package:flutter/material.dart';

 

main() 함수는 앱의 시작점입니다. runApp() 함수에 MyApp() 인스턴스를 전달합니다.

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

 

StatelessWidget 클래스는 상태가 없는 위젯을 정의하는 데 사용됩니다. 

상태가 없다는 것은 한 번 그려진 후 다시 그리지 않는 경우를 의미합니다. 이러한 클래스는 프로퍼티로 변수를 가지지 않습니다. StatelessWidget 클래스는 위젯을 생성할 때 호출되는 build() 메서드를 가지고 있으며 실제로 화면에 그릴 위젯을 작성해 반환합니다. Material App 인스턴스를 반환하는데, theme 같은 위젯의 속성을 설정합니다.

home에 작성하는 위젯이 실제 이 앱이 표시하는 위젯이 됩니다.

home 부분은 상태가 있기 때문에 StatefulWidget 클래스로 따로 정의합니다.

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      //debugShowCheckedModeBanner: false,  //Debug Banner 제거
      home: const MyHomePage(title: '모두의 마블 가격 계산'),
    );
  }
}

 

StatefulWidget 클래스를 상속받아 createState()를 재정의하여 실제 대부분의 구현이 이루어질 _MyHomePageState 클래스의 인스턴스를 반환합니다.

위젯을 그리는 build method에서 appBar 와 body 부분을 클래스로 분리 구현합니다.

 

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {

 var _index = 0;
  var _totalCost = 0;
  //...
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: MyAppBar(widget.title),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    return ListView(
      children: [
        _buildTop(),
        _drawLine(),
        _buildCityButton(),
        _buildCheckBox(),
        _drawLine(),
        _buildIsland(),
        _drawLine(),
        _buildCostButton(),
      ],
    );
  }

 


 

 body: Row (
        mainAxisSize: MainAxisSize.max, //가로로 꽉 채우기
        mainAxisAlignment: MainAxisAlignment.center, //가로 방향으로 가운데 정렬하기
        crossAxisAlignment: CrossAxisAlignment.center, //세로 방향으로 가운데 정렬하기

mainAxis는 위젯의 기본방향을 나타냄.
Row는 오른쪽, Column은 아래쪽이 mainAxis가 됩니다.
crossAxis는 기본방향의 반대방향을 나타냄. 

<너비 최대>
width: double.infinity,
height: double.infinity
ex)
Container(
height: 0.1,
width: double.infinity,

//ListView
body: ListView (
        scrollDirection: Axis.vertical,
        children: <Widget>[
          ListTile(
            leading: Icon(Icons.home), //왼쪽
            title: Text('Home'), //중앙
            trailing: Icon(Icons.navigate_next), //오른쪽
            onTap: (){},
          ),
          ListTile(
            leading: Icon(Icons.event),
            title: Text('Event'),
            trailing: Icon(Icons.navigate_next),
            onTap: (){},
          ),
          ListTile(
            leading: Icon(Icons.camera),
            title: Text('Camera'),
            trailing: Icon(Icons.navigate_next),
            onTap: (){},
          ),
        ],
      )
  
  
//PageView
body:  PageView(
        children: <Widget>[
          Container(
            color: Colors.red,
          ),
          Container(
            color: Colors.green,
          ),
          Container(
            color: Colors.blue,
          ),
        ],
      )
  

//TabBar, Tab, TabBarView
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: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

case1) // AppBar와 TabBar가 한 영역에
class MyHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          bottom: TabBar(
            indicatorColor: Colors.lime,
            tabs: [
              Tab(icon: Icon(Icons.directions_car)),
              Tab(icon: Icon(Icons.directions_transit)),
              Tab(icon: Icon(Icons.directions_bike)),
            ],
          ),
          title: Text('Tabs Demo'),
        ),
        body: TabBarView(
          children: [
            Icon(Icons.directions_car),
            Icon(Icons.directions_transit),
            Icon(Icons.directions_bike),
          ],
        ),
      ),
    );
  }
}

//Case 2: TabBar를 AppBar와 분리함, AppBar에 그라데이션 속성
class MyHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        toolbarHeight: 70,
        title: Text('Flutter Demo',
          style: TextStyle(
            fontWeight: FontWeight.w600,
            fontFamily: 'Poppins',
            fontSize: 36.0,
          ),
        ),
        centerTitle: true,
        flexibleSpace: Container(
          decoration: BoxDecoration(
              gradient: LinearGradient(
                begin: const FractionalOffset(0.0, 0.0),
                end: const FractionalOffset(1.0, 1.0),
                colors: <Color>[
                  const Color(0xFF3366FF),
                  const Color(0xFF00CCFF),
                ],
                stops: <double>[0.0, 1.0],
                tileMode: TileMode.clamp,
              )
          ),
        ),
      ),

      body: DefaultTabController(
        length: 3,
        child: Column(
          children: <Widget>[
          Container(
            constraints: BoxConstraints(maxHeight: 150.0), //Tab Height
            child: Material(
              color: Colors.indigo, //Tab color
              child: TabBar(
                tabs: [
                  Tab(icon: Icon(Icons.directions_car)),
                  Tab(icon: Icon(Icons.directions_transit)),
                  Tab(icon: Icon(Icons.directions_bike)),
                ],
              ),
            ),
          ),
            Expanded( //자식위젯의 크기를 최대한으로 확장시켜주는 위젯
              child: TabBarView(
                children: [
                  Icon(Icons.directions_car),
                  Icon(Icons.directions_transit),
                  Icon(Icons.directions_bike),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
}


//BottomNavigationBar
bottomNavigationBar: BottomNavigationBar(items: [
        BottomNavigationBarItem(
          icon: Icon(Icons.home),
          title: Text('Home'),
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.person),
          title: Text('Profile'),
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.notifications),
          title: Text('Notification'),
        ),
      ]),


//Center + Card
Center (
child: [Widget],
),

ex)
Center (
child: Card(
shape: RoundedRectangleBorder (
borderRadius: BorderRadius.circular(16.0),
),
elevation: 4.0, //그림자 깊이
child: Container(
width: 200,
height: 200,
),
),
),

Padding (
padding: const EdgeInsets.all(40.0),
child: [Widget],
),

Align (
alignment: Alignment.bottomRight,
child: [Widget],
),

Column (
children: <Widget> [
Expanded( //자식위젯의 크기를 최대한으로 확장시키는 위젯
flex: [비율], //기본값은 1
child: [Widget],
),
Expanded(
child: [Widget],
),
Expanded(
child: [Widget],
),

],
),

//Button
RaisedButton
FlatButton
IconButton (
icon: Icon(Icons.add),
color: Colors.red,
iconSize: 100.0,
//혹은
icon: Icon(
Icons.add,
color: Colors.red,
size: 100.0,
),
onPressed: (){},
),
FloatingActionButton //입체감있는 둥근버튼 위젯, 아이콘을 표시하는데 사용
ex)
floatingActionButton: FloatingActionButton(
          child: Icon(Icons.eleven_mp),
          onPressed: () => FlutterDialog(),
        ),
        floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,

CircularProgressIndicator
LinearProgressIndicator

CircleAvatar //프로필 화면 등에 많이 사용되는 원형 위젯


TextField //입력용 위젯의

TextField(
decoration: InputDecoration(
border: OutlineInputBorder(), //외곽선
labelText: 'Input here',
),
),


var isChecked = false;
CheckboxListTile(
title: Text('CheckBox'),
value: isChecked,
onChanged: (value){
setState(){
isChecked = value;
}
},
),

SwitchListTile

RadioListTile
ex)
enum Gender { MAN, WOMAN }
Gender _gender = Gender.MAN;

RadioListTile(
                title: Text('Man'),
                //leading: Radio(
                  value: Gender.MAN,
                  groupValue: _gender,
                  onChanged: (value) {
                    setState(() {
                      _gender = value as Gender;
                    });
                  },
                //),
              ),
              RadioListTile(
                title: Text('Woman'),
                //leading: Radio(
                value: Gender.WOMAN,
                groupValue: _gender,
                onChanged: (value) {
                  setState(() {
                    _gender = value as Gender;
                  });
                },
                //),
              ),

DropDownButton //여러 아이템중 하나를 고를 수 있는 콤보박스형태의 위젯
//StatefulWidget 사용

ex)
final _valueList = ['김정현','김성준','김여진'];
var _selectedValue = '김정현';
DropdownButton(
                value: _selectedValue,
                items: _valueList.map((value) {
                  return DropdownMenuItem(value: value, child: Text(value));
                }).toList(),
                onChanged: (value) {
                  setState(() {
                    _selectedValue = value as String;
                  });
                },
              ),
  
  
DatePicker
TimePicker


글자나 그림같이 이벤트 프러퍼티가 없는 위젯에 이벤트를 적용하고 싶을때 사용하는 위젯
GestureDetector, InkWell(클릭시 물결효과가 나타남)


Animation 을 지원하는 위젯
1. Hero
ex)
class HeroPage extends StatelessWidget {
..
body: Center(
child: GestureDetector(
onTap:() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => HeroDetailPage()),
);
},
child: Hero(
tag: 'image', //여기서 작성한 태그와 두번째 페이지의 태그가 동일해야함
child: Image.asset('assets/sample.jpg',
width: 100, height:100,
),
),
),
),
}

class HeroDetailPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar : AppBar(
title: Text('Hero Detail),
}.
body: Hero(
tag: 'image',
child: Image.asset('assets/sample.jpg'),
),
),
}
}

2. AnimatedContainer
한 화면내에서 setState() 를 호출하여 화면을 새로 그릴때 변경된 프로퍼티에 의해 애니메이션되도록 해준다.
AnimatedContainer(
duration: Duration(seconds: 1), //1초 동안 애니메이션 적용
width:100.0,
height:150.0,
child: [위젯]
curve: Curves.fastOutSlowIn, //미리 정의된 애니메이션 효과

3. SilverAppBar, SilverFillRemaining
화면 헤더를 동적으로 표현하는 위젯, 
헤더를 위로 스크롤하면 헤더부분이 작아지면서 헤더하단에 있던 정적인 내용만 보이는 AppBar 형태로 애니메이션됨

4. 3. SilverAppBar, SilverList
ListView를 사용하여 Silver효과를 주고 싶을때


쿠퍼티노(Cupertino) 디자인 : iOS
CupertinoNavigationBar (AppBar)
CupertinoSwitch
CupertinoButton
CupertinoAlertDialog
CupertinoPicker

 

 

 

ListView 아래에 GridView를 사용할 때는 아래 스크롤 방지를 넣어줘야 화면에 그려짐
physics: NeverScrollableScrollPhysics(),//이 리스트의 스크롤 동작금지하여 바깥쪽(super) 스크롤이 동작하게끔
    shrinkWrap: true, //스크롤 가능한 객체안에 다시 스크롤 객체를 넣는 경우 shrinkWrap 을 true로 설정해야 한다.

final List<String> items = <String>[
      "Item 1",
      "Item 2",
      "Item 3",
      "Item 4",
      "Item 5",
      "Item 6",
    ];
    return GridView.builder(
      padding: const EdgeInsets.all(20.0),
      physics: NeverScrollableScrollPhysics(), // to disable GridView's scrolling
      shrinkWrap: true, // You won't see infinite size error
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 5,
        childAspectRatio: MediaQuery.of(context).size.width /
            (MediaQuery.of(context).size.height / 4),
      ),
      itemCount: items.length,
      itemBuilder: (context, index) {
        return GridTile(child: Text(items[index]));
      },
    );

 

 

<반복되는 위젯은 다트의 List.generate() 생성자로 위젯을 만들자>

children : List.generate(7, (int index) {

           final weatherIcon = _getWeatherIcon(forecast.day[index]);

           return TableRow(

                      children: [

                                 TableCell(

                                             child: Icon(weatherIcon),

                                 ),

                                 TableCell(

                                            child: //..

                                 ),

                                 //

                                

                                 ..

                      ],

           );

}),

 

<Custom widget>

class FancyButton extends StatefulWidget {

           final VoidCallback on Pressed;

           final Widget child;

          

           final void Function(int) onTabChange;       

          

           const FancyButton({Key key, this.onPressed, this.child}) : super(key : key);

           //..

}

 

class _FancyButtonState extends State<FancyButton> {

           @override

           Widget build(BuildContext context) {

                      return Container(

                                 child: RaisedButton(

                                             child: widget.child,

                                             onPressed: widget.onPressed,

                                 ),

                      );

           }

}

 

Widget build(BuildContext context) {

           final incButton = FancyButton(

                      child: Text(/..

                      onPressed: _incrementCount,

 


 

//Dart 의 Singleton 패턴
static final DBHelper _dbHelper = DBHelper._internal(); //_internal : private 생성자
DBHelper._internal();
factory DBHelper() {
  return _dbHelper;
}
반응형

'Flutter' 카테고리의 다른 글

Flutter - 주요 단축기/배너제거/플랫폼구분  (0) 2022.02.06
Flutter - File IO / Delay / 가로모드  (0) 2022.02.06
Flutter - ToJson  (0) 2022.02.06
Flutter - Database  (0) 2022.02.06
Flutter App Bar/화면크기조절/화면전환  (0) 2022.02.06