Posts flutter (rn 개발자를 위한 정리 4)
Post
Cancel

flutter (rn 개발자를 위한 정리 4)

Props

  • ReactNative 에서 대부분의 컴포넌트는 매겨변수나 속성을 props로 전달함

  • Flutter에서는 매개변수가 있는 생성자에서 받은 속성을 final로 표시된 지역변수나 함수에 할당함

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
    // Flutter
    class CustomCard extends StatelessWidget {
      
      CustomCard({@required this.index, @required this.onPress});
      final index;
      final Function onPress;
      
      @override
      Widget build(BuildContext context) {
      return Card(
        child: Column(
          children: <Widget>[
            Text('Card $index'),
            FlatButton(
              child: const Text('Press'),
              onPressed: this.onPress,
            ),
          ],
        ));
      }
    }
        ...
    //Usage
    CustomCard(
      index: index,
      onPress: () {
        print('Card $index');
      },
    )
    

로컬 저장소

  • 많은 데이터가 아니라면, 키-값 쌍 형태의 저장소인 shared_preference 플러그인으로 기본 타입(booleans, floats, int, long, string) 데이터를 읽고 쓸 수 있음

    • ios에선 NSUserDefaults, Android에선 SharedPreferences를 감싸고 있음
    • 다음과 같이 의존성을 추가한 후, import하여 사용
    1
    2
    3
    4
    
    dependencies:
      flutter:
        sdk: flutter
      shared_preferences: ^0.4.3
    
    1
    2
    
    // Dart
    import 'package:shared_preferences/shared_preferences.dart';
    
  • 데이터 저장 : SharedPreferences 클래스의 setter 메소드 사용

    • setInt, setBool, setString 과 같은 방식의 다양한 기본형 타입에서 사용가능하다.
  • 데이터 읽기 : SharedPreferences 클래스의 getter 메소드 사용

    • getInt, getBool, getString 과 같은 함수 사용
1
2
3
4
5
6
7
SharedPreferences prefs = await SharedPreferences.getInstance();
_counter = prefs.getInt('counter');
prefs.setInt('counter', ++_counter);
setState(() {
  _counter = _counter;
});

Routing

  • 플러터에서는 새로운 화면도 위젯이다.
  • Navigator 위젯을 통해 이동

스택 네비게이션

  • Route 위젯 : 앱의 화면 또는 페이지를 추상화 한 것

  • Navigator 위젯 : route를 관리하는 위젯

    • 스택을 사용하여 자식 위젯(Route 객체)들을 관리함
    • Navigator.push, Navigator.pop 같은 메서드를 제공
    • Route 목록은 MaterialApp 위젯에서 지정가능
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    
    // Flutter
    class NavigationApp extends StatelessWidget {
      // This widget is the root of your application.
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
                ...
          routes: <String, WidgetBuilder>{
            '/a': (BuildContext context) => usualNavscreen(),		//Named route 지정
            '/b': (BuildContext context) => drawerNavscreen(),
          }
                ...
      );
      }
    }
    
  • Named route로 이동하기 위해, of메서드를 사용하여 Navigator 위젯의 BuildContext를 지정함.

    • Route 의 이름은 pushNamed 함수를 통해 전달하여 특정한 route로 이동함
    1
    
    Navigator.of(context).pushNamed('/a');
    
  • Navigator의 push 메서드로 주어진 route를 가장 가까운 context를 가진 네비게이터에 추가하고 이동할 수 있음.

    • 아래는 MaterialPageRoute 위젯이 플랫폼에 적함한 전환효과와 함께 전체 스크린을 modal route로 전환시킴.
    • 필수 매개변수로 WidgetBuilder를 넣어줘야함
    1
    2
    
    Navigator.push(context, MaterialPageRoute(builder: (BuildContext context)
     => UsualNavscreen()));
    

탭 네비게이션

  • Flutter는 drawer 및 탭 네비게이션을 위한 여러 특수 위젯을 제공함

    • TabController: TabBar와 TabBarView 사이에서 탭 선택을 조정
      • lengths : 전체 탭 개수, vsync : 프레임이 상태 변화를 할때마다 알려주기 위한 TickerProvider
    • TabBar: 탭의 수평 열을 보여줌
    • Tab: Material 디자인으로 TabBar 탭을 생성
    • TabBarView: 현재 선택된 탭에 맞는 위젯을 보여줌.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    // Flutter
    TabController controller=TabController(length: 2, vsync: this);
      
    TabBar(
      tabs: <Tab>[
        Tab(icon: Icon(Icons.person),),
        Tab(icon: Icon(Icons.email),),
      ],
      controller: controller,
    ),
    
  • TickerProvider : Ticker 객체를 제공할 수 있는 클래스에 의해 구현된 인터페이스

    • Ticker : 프레임이 트리거 될때마다 알림을 받아야하는 모든 객체에서 사용할 수 있음

      • 보통 AnimationController를 통해 간접적으로 사용함

      • AnimationController는 Ticker를 얻기위해 TickerProvider가 필요함

      • State에서 AnimationController를 만들고 있다면, TickerProviderStateMixin 또는 SingleTickerProviderStateMixin 클래스를 사용하여 적절한 TickerProvide를 얻음

  • 아래 Scaffold 위젯은 새로운 TabBar 위젯을 감싸고 2개의 탭을 생성

    • TabBarView는 body 인자로 전달됨.
    • TabBar 위젯의 탭에 해당하는 모든 화면은 같은 TabController를 가지고 있는 TabBarView 위젯의 자식들임
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    class _NavigationHomePageState extends State<NavigationHomePage> with SingleTickerProviderStateMixin {
      TabController controller=TabController(length: 2, vsync: this);
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          bottomNavigationBar: Material (
            child: TabBar(
              tabs: <Tab> [
                Tab(icon: Icon(Icons.person),)
                Tab(icon: Icon(Icons.email),),
              ],
              controller: controller,
            ),
            color: Colors.blue,
          ),
          body: TabBarView(
            children: <Widget> [
              home.homeScreen(),
              tabScreen.tabScreen()
            ],
            controller: controller,
          )
        );
      }
    }
    

Drawer 네비게이션

  • Drawer 위젯을 Scaffold와 조합하여 material 디자인의 drawer를 만들 수 있음
    • Drawer을 Scaffold 위젯으로 감싸야함
  • Drawer 위젯은 Scaffold 의 왼쪽/오른쪽 모서리에서 슬라이트 형태로 등장하여 앱의 네비게이션 링크를 보여줌
    • Button이나 Text 또는 여러 아이템 목록을 Drawer 위젯의 자식으로 보여줄 수 있음
    • 아래 예제는 ListTile 을 자식으로 보여줌
  • Scaffold 위젯은 Drawer을 사용할 수 있을 때 Drawer로 연결되는 IconButton을 띄어주는 AppBar 위젯을 포함함.
    • 왼쪽 위에 버튼 눌러서 Drawer 가능하도록
  • Scaffold를 사용하면 edge-swipe 동작을 했을때 자동으로 Drawer가 나타남
    • 왼쪽에서 당겼을때 drawer가 나오도록
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Flutter
@override
Widget build(BuildContext context) {
  return Scaffold(
    drawer: Drawer(
      child: ListTile(
        leading: Icon(Icons.change_history),
        title: Text('Screen2'),
        onTap: () {
          Navigator.of(context).pushNamed('/b');
        },
      ),
      elevation: 20.0,
    ),
    appBar: AppBar(
      title: Text('Home'),
    ),
    body: Container(),
  );
}

제스처 감지와 터치 이벤트 다루기

  • Flutter 제스처는 2개의 층이 있음
    • 첫번째 층 : 포인터의 위치와 움지기임을 표현하는 원시 포인터 이벤트(터치, 마우스, 스타일러스 동작)
    • 두번째 층 : 하나 이상의 포인터 움직임으로 만들어진 동작을 표현하는 제스처

Click/Press 리스너

  • ReactNative 에서는 Touchable 컴포넌트를 사용함

  • Flutter에서는 onPress : field 를 가진 버튼이나 터치 가능한 위젯을 사용함

    • 또는 어떤 위젯이건 GestureDetector로 감싸서 제스처 감지를 추가할 수도 있음
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    // Flutter
    GestureDetector(
      child: Scaffold(
        appBar: AppBar(
          title: Text('Gestures'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text('Tap, Long Press, Swipe Horizontally or Vertically '),
            ],
          )
        ),
      ),
      onTap: () {
        print('Tapped');
      },
      onLongPress: () {
        print('Long Pressed');
      },
      onVerticalDragEnd: (DragEndDetails value) {
        print('Swiped Vertically');
      },
      onHorizontalDragEnd: (DragEndDetails value) {
        print('Swiped Horizontally');
      },
    );
    

HTTP 네트워크 요청 만들기

  • Flutter에서는 http 패키지를 사용함. 아래처럼 의존성 추가
1
2
3
4
dependencies:
  flutter:
    sdk: flutter
  http: <latest_version>
  • HTTP 클라이언트 지원에 dart:io 를 사용함.

    • dart:io 를 import하여 사용한다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    import 'dart:io';
    ...
    final url = Uri.https('httpbin.org', 'ip');
    final httpClient = HttpClient();
    _getIPAddress() async {
      var request = await httpClient.getUrl(url);
      var response = await request.close();
      var responseBody = await response.transform(utf8.decoder).join();
      String ip = jsonDecode(responseBody)['origin'];
      setState(() {
        _ipAddress = ip;
      });
    } 
    

Form input

TextField 위젯 사용

  • TextEditingController클래스를 사용하여 TextField 위젯을 관리함
  • 위 컨트롤러는 텍스트 필드가 수정될때마다 리스너에게 알림
  • 리스너는 사용자가 필드에 입력한 내용을 알기 위해 text와 selection 속성을 읽음
    • text 속성을 활용하여 TextField 에 있는 텍스트에 접근가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Flutter
final TextEditingController _controller = TextEditingController();
      ...
TextField(
  controller: _controller,
  decoration: InputDecoration(
    hintText: 'Type something', labelText: 'Text Field '
  ),
),
RaisedButton(
  child: Text('Submit'),
  onPressed: () {
    showDialog(
      context: context,
        child: AlertDialog(
          title: Text('Alert'),
          content: Text('You typed ${_controller.text}'),
        ),
     );
   },
 ),
)

Form

  • Form 위젯의 자식으로 TextFormField 위젯들을 가져서 구현가능
  • TextFormField 위젯에는 form이 저장될 때 실행되는 콜백을 받는 onSaved라는 매개변수가 있음
  • FormState 객체는 Form 하위에 있는 각 FormField 를 저장하고, 초기화하고, validation하기 위해 사용함
  • Form의 부모 context와 함께 Form.of를 사용하거나, Form 생성자에 GlobalKey를 넘기고 GlobalKey.currentState를 호출하여 FormState를 가져올 수 있음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
final formKey = GlobalKey<FormState>();

...

Form(
  key:formKey,
  child: Column(
    children: <Widget>[
      TextFormField(
        validator: (value) => !value.contains('@') ? 'Not a valid email.' : null,
        onSaved: (val) => _email = val,
        decoration: const InputDecoration(
          hintText: 'Enter your email',
          labelText: 'Email',
        ),
      ),
      RaisedButton(
        onPressed: _submit,
        child: Text('Login'),
      ),
    ],
  ),
)
1
2
3
4
5
6
7
8
9
10
11
12
13
void _submit() {
  final form = formKey.currentState;
  if (form.validate()) {
    form.save();
    showDialog(
      context: context,
      child: AlertDialog(
        title: Text('Alert'),
        content: Text('Email: $_email, password: $_password'),
      )
    );
  }
}

Platform-specific 코드

1
2
3
4
5
6
7
8
9
10
// Flutter
if (Theme.of(context).platform == TargetPlatform.iOS) {
  return 'iOS';
} else if (Theme.of(context).platform == TargetPlatform.android) {
  return 'android';
} else if (Theme.of(context).platform == TargetPlatform.fuchsia) {
  return 'fuchsia';
} else {
  return 'not recognised ';
}

## 디버깅

  • DevTools를 사용
    • 프로파일링, 힙검사, 위젯트리 조사, 로깅 진단, 디버깅, 실행된 코드라인 관찰, 메모리 누수 및 메모리 조각화 디버깅 지원

인앱 개발자 메뉴에 접근

  • IDE를 사용중이면, IDE 도구를 사용할 수 있음
  • flutter run을 사용했다면, 터미널 창에서 h를 눌러서 접근가능
    • 또는 다양한 터미널 단축키등이 있음 : 링크

애니메이션

  • Animation 클래스
    • 현재 값과 상태(완료 또는 해제)를 알고 있는 추상 클래스
  • AnimationController 클래스
    • 애니메이션을 앞뒤로 재생하거나, 중지, 특정 값으로 설정 하는 등의 모션을 커스터마이징 가능

간단한 fade-in 애니메이션

  • AnimationController 객체를 만들고, 기간을 지정

    • 기본적으로 AnimationController는 특정 기간 동안 0.0 ~ 1.0 범위에서 값이 선형적으로 증가함

    • 일반적으로 초당 60 FPS로, 기기에서 새로운 프레임을 보여줄 준비가 됐을 때마다 새로운 값을 생성함

  • AnimationController를 정의할 때, vsync 객체를 넘겨줘야함

    • 그래야 화면이 꺼져있을때 애니메이션에 불필요한 리소스를 소비하지 않음

    • 클래스 정의에 TickerProviderStateMixin을 추가하여 상태 저장객체를 vsync로 사용할 수 있음

    • AnimationController 에는 생성자에 vsync 인수를 사용하여 만들어진 TickerProvider가 필요함

  • Tween은 시작값과 끝 값 사이의 보간 또는 입력 범위에서 출력 범위까지의 매핑을 표현함

    • 애니메이션과 함께 Tween 객체를 사용하기 위해, Tween 객체의 animate 메서드를 호출한 뒤 수정하고 싶은 Animation 객체로 넘김
  • 아래 예제에서는 FadeTransition 위젯을 사용하고, opacity 속성을 animation 객체에 매핑시킴

    • 애니메이션을 시작하기 위해 controller.forward()를 사용함
    • fling() 이나 repeat()를 사용하여 다른 동작도 가능
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    
    // Flutter
    import 'package:flutter/material.dart';
      
    void main() {
      runApp(Center(child: LogoFade()));
    }
      
    class LogoFade extends StatefulWidget {
      _LogoFadeState createState() => _LogoFadeState();
    }
      
    class _LogoFadeState extends State<LogoFade> with TickerProviderStateMixin {
      Animation animation;
      AnimationController controller;
      
      initState() {
        super.initState();
        controller = AnimationController(
            duration: const Duration(milliseconds: 3000), vsync: this);
        final CurvedAnimation curve =
        CurvedAnimation(parent: controller, curve: Curves.easeIn);
        animation = Tween(begin: 0.0, end: 1.0).animate(curve);
        controller.forward();
      }
      
      Widget build(BuildContext context) {
        return FadeTransition(
          opacity: animation,
          child: Container(
            height: 300.0,
            width: 300.0,
            child: FlutterLogo(),
          ),
        );
      }
      
      dispose() {
        controller.dispose();
        super.dispose();
      }
    }
    

카드에 스와이프 애니메이션을 추가하는 법

  • Dismissble 위젯을 사용하여 자식 위젯을 감쌈
1
2
3
4
5
6
7
8
9
child: Dismissible(
  key: key,
  onDismissed: (DismissDirection dir) {
    cards.removeLast();
  },
  child: Container(
    ...
  ),
),

React Native 와 Flutter 사이에 상응하는 위젯 목록

This post is licensed under CC BY 4.0 by the author.

flutter (rn 개발자를 위한 정리 3)

react 작동원리부터 tailwindcss 사용까지

Comments powered by Disqus.