본문 바로가기

Flutter & Dart

공부하다 배운 Flutter 꿀팁 무작정 적어두기 (6)

video_player의 경우 버전이 낮으면 init 시에 에러발생함
GridView를 쓸 때는 children으로 하는 게 아니라 ListView처럼 builder로 하는 게 낫다.
gridDelegate는 컨트롤러라기보다는 GridView를 구성하는 데에 도움을 주는 헬퍼 정도..
FadeInImage는
넘치는 텍스트를 다 보이게 하고싶으면, expanded 위젯을 활용
키보드가 실행된다거나 하는 이유로 화면이 resize되는 이슈를 줄이기 위한 방법
-> Scaffold 속성 중 resizeToAvoidBottomInset을 False로 세팅
여러개의 리스트를 보여주기 위한 여러가지 방법
GridView, ListTile!
Material Design의 세팅값들이 자동으로 적용되는 게 싫은 경우, main.dart에서 ThemeData에서 적용하기 싫은 값들에 Colors.transparent를 설정해주면 됨!
여러개의 텍스트를 하나로 묶어서 사용하고 싶을 땐 RichText + TextSpan 위젯을 사용!
Dismissible 위젯 : 슬라이드해서 보여지는 걸 다르게 할 수 있음! 마치 틱톡처럼!
(얘는 사이드 프로젝트에 꼭 사용해야겠다!)
Dismissible 위젯은 꼭 슬라이드했을 때 onDismissed 콜백 함수가 작동해야 오류가 안 뜸
Animation Builder 없이도 Animation 만들기!
RotationTransition 위젯의 turns 속성에 Animation<double>을 정의하면 애니메이션을 작동하게 할 수 있음.

  late final Animation<double> _animation =
      Tween(begin: 0.0, end: 1.0).animate(_animationController);
 RotationTransition(
              turns: _animation,)

Tween은 begin과 end 값을 더블로 받아서 이 과정에 사용할 수 있음. 소스는 아래와 같음.


한번 이렇게 컨트롤러를 정의해두면 속성의 설정에 맞춰 다른 생성을 진행해 다방면의 위젯에 활용할 수 있음
AnimatedModalBarrier 위젯을 활용하면 활성화된 슬라이드 외의 다른 부분을 어둡게 오버레이+비활성화 시킬 수 있다


Scroll 기능을 커스터마이징해서 사용할 수 있게끔 나온 위젯 = CustomScrollView 위젯.
slivers 속성에서 SliverAppBar를 사용!


VerticalDivider 사용하면 말 그대로 수직선을 세팅 가능! (다만 높이가 있는 위젯에 대해서만 작동하므로, 높이가 없는 경우 SizedBox 위젯으로 감싸주고 height를 쎄팅한다.


FractionallySizedBox는 부모 위젯에 비례한 사이즈 조절할 때 사용


CloseButton 위젯 : 닫기 기능을 하는 X 버튼 위젯


ListWheelScrolView : 리스트를 수레바퀴처럼 돌아가는 방식으로 스크롤할 수 있음!

body: ListWheelScrollView(
        useMagnifier: true,
        magnification: 1.5,
        itemExtent: 200,
        children: [
          for (var x in [1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3, 1, 1])
            FractionallySizedBox(
              widthFactor: 1,
              child: Container(
                color: Colors.teal,
                alignment: Alignment.center,
                child: const Text(
                  'Pick me',
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 39,
                  ),
                ),
              ),
            ),
        ],
      ),

CupertinoActivityIndicator() : 아이폰용 로딩 인디케이터
CircularProgressIndicator() : 안드로이드용 로딩 인디케이터
CircularProgressIndicator.adaptive() : 스마트폰 기종에 따라 다른 로딩 인디케이터를 표시함


어플 버전정보를 보여주고 싶을 때는 만들어져 있는 기능이나 위젯을 이용하면 된다!

1. ListView > ListTile > onTap -> showAboutDialog 메소드 사용
2. 그냥 ListView 아래에 AboutListTile을 사용해도 됨! 커스터마이징하기에는 1번이 편함
(아래 코드 참고)

ListTile
body: ListView(
        children: [
          ListTile(
            onTap: () => showAboutDialog(
                context: context,
                applicationVersion: "1.0",
                applicationLegalese:
                    "All rights reserved. Please don't copy me."),
            title: const Text(
              "About",
              style: TextStyle(
                fontWeight: FontWeight.w600,
              ),
            ),
            subtitle: const Text("About this app......"),
          ),
          const AboutListTile()
        ],
      ),


Date, Time, Period를 설정할 때 꿀팁 함수들
= showDatePicker, showTimePicker, showDateRangePicker
(아래 소스 참고)

body: ListView(
        children: [
          ListTile(
            onTap: () async {
              final date = await showDatePicker(
                context: context,
                initialDate: DateTime.now(),
                firstDate: DateTime(1992),
                lastDate: DateTime(2024),
              );
              print(date);
              final time = await showTimePicker(
                context: context,
                initialTime: TimeOfDay.now(),
              );
              print(time);
              final booking = await showDateRangePicker(
                context: context,
                firstDate: DateTime(1992),
                lastDate: DateTime(2024),
                builder: (context, child) {
                  return Theme(
                    data: ThemeData(
                      appBarTheme: const AppBarTheme(
                        foregroundColor: Colors.white,
                        backgroundColor: Colors.deepPurple,
                      ),
                    ),
                    child: child!,
                  );
                },
              );
              print(booking);
            },
            title: const Text("What is your birthday?"),
          ),
        ],
      ),

스위치와 체크박스를 사용하는 방법

 

body: ListView(
        children: [
          CupertinoSwitch(
            value: _notifications,
            onChanged: _onNotificationsChanged,
          ),
          Switch(
            value: _notifications,
            onChanged: _onNotificationsChanged,
          ),
          Switch.adaptive(
            value: _notifications,
            onChanged: _onNotificationsChanged,
          ),
          SwitchListTile(
            value: _notifications,
            onChanged: _onNotificationsChanged,
            title: const Text("Enable Notification"),
            subtitle: const Text("detailed information"),
          ),
          Checkbox(
            value: _notifications,
            onChanged: _onNotificationsChanged,
          ),
          CheckboxListTile(
            activeColor: Colors.black,
            checkColor: Colors.white,
            value: _notifications,
            onChanged: _onNotificationsChanged,
            title: const Text("Enable notifications"),
          ),


로그아웃 기능 구현 시 참고

ListTile(
            title: const Text("Log out (iOS)"),
            textColor: Colors.red,
            onTap: () {
              showCupertinoDialog(
                context: context,
                builder: (context) => CupertinoAlertDialog(
                  title: const Text("Are you sure?"),
                  content: const Text("Please don't go"),
                  actions: [
                    CupertinoDialogAction(
                      onPressed: () => Navigator.of(context).pop(),
                      child: const Text("No"),
                    ),
                    CupertinoDialogAction(
                      onPressed: () => Navigator.of(context).pop(),
                      isDestructiveAction: true,
                      child: const Text("Yes"),
                    ),
                  ],
                ),
              );
            },
          ),
          ListTile(
            title: const Text("Log out (Android)"),
            textColor: Colors.red,
            onTap: () {
              showDialog(
                context: context,
                builder: (context) => AlertDialog(
                  title: const Text("Are you sure?"),
                  content: const Text("Please don't go"),
                  actions: [
                    IconButton(
                      onPressed: () => Navigator.of(context).pop(),
                      icon: const FaIcon(FontAwesomeIcons.car),
                    ),
                    TextButton(
                      onPressed: () => Navigator.of(context).pop(),
                      child: const Text("Yes"),
                    ),
                  ],
                ),
              );
            },
          ),
          ListTile(
            title: const Text("Log out (iOS / Bottom)"),
            textColor: Colors.red,
            onTap: () {
              showCupertinoModalPopup(
                context: context,
                builder: (context) => CupertinoActionSheet(
                  title: const Text("Are you sure?"),
                  actions: [
                    CupertinoActionSheetAction(
                      isDefaultAction: true,
                      onPressed: () => Navigator.of(context).pop(),
                      child: const Text("Not log out"),
                    ),
                    CupertinoActionSheetAction(
                      isDestructiveAction: true,
                      onPressed: () => Navigator.of(context).pop(),
                      child: const Text("Yes Please"),
                    ),
                  ],
                ),
              );
            },
          ),


자동회전 시 바뀌는 화면 비율을 조정하기 위한 방법
-> Scaffold를 OrientationBuilder로 감싸고 세로화면일 때와 가로화면일 때의 코드를 다르게 세팅한다.
orientation 값이 portrait일 때가 세로, landscape일 때가 가로

@override
  Widget build(BuildContext context) {
    return OrientationBuilder(
      builder: (context, orientation) {
        return Scaffold(
          body: SafeArea(
            child: Padding(
              padding: const EdgeInsets.symmetric(
                horizontal: Sizes.size40,
              ),
              child: Column(
                children: [
                  Gaps.v80,
                  const Text(
                    "Sign up for TikTok",
                    style: TextStyle(
                      fontSize: Sizes.size24,
                      fontWeight: FontWeight.w700,
                    ),
                  ),
                  Gaps.v20,
                  const Text(
                    "Create a profile, follow other accounts, make your own videos, and more.",
                    style: TextStyle(
                      fontSize: Sizes.size16,
                      color: Colors.black45,
                    ),
                    textAlign: TextAlign.center,
                  ),
                  Gaps.v40,
                  if (orientation == Orientation.portrait) ...[
                    GestureDetector(
                      onTap: () => _onEmailTap(context),
                      child: const AuthButton(
                          icon: FaIcon(FontAwesomeIcons.user),
                          text: "Use email or password"),
                    ),
                    Gaps.v16,
                    const AuthButton(
                        icon: FaIcon(FontAwesomeIcons.apple),
                        text: "Continue with Apple"),
                  ],
                  if (orientation == Orientation.landscape) ...[
                    Row(
                      children: [
                        Expanded(
                          child: GestureDetector(
                            onTap: () => _onEmailTap(context),
                            child: const AuthButton(
                                icon: FaIcon(FontAwesomeIcons.user),
                                text: "Use email or password"),
                          ),
                        ),
                        Gaps.h16,
                        const Expanded(
                          child: AuthButton(
                              icon: FaIcon(FontAwesomeIcons.apple),
                              text: "Continue with Apple"),
                        ),
                      ],
                    ),
                  ],
                ],
              ),
            ),
          ),


SystemChrome을 활용한 자동회전 방지 방법
: main.dart에서 다음과 같이 세팅

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await SystemChrome.setPreferredOrientations(
    [
      DeviceOrientation.portraitUp,
    ],
  );


음성이 자동으로 재생되는 영상은 많은 브라우저에서 권한을 막고 있음. 해결법은 간단. 웹일 때만 비디오가 초기화될 때 음소거 처리하면 됨. 

** flutter에는 미리 정의된 constant variable들이 있고, k로 시작함

  void _initVideoPlayer() async {
    await _videoPlayerController.initialize();
    await _videoPlayerController.setLooping(true);
    if (kIsWeb) {
      await _videoPlayerController.setVolume(0);
    }
    _videoPlayerController.play();
    setState(() {});
    _videoPlayerController.addListener(_onVideoChange);
  }


음소거 버튼 만들기!

1. 버튼 추가
: 뮤트 플래그가 참이면 음소거버튼이, 거짓이면 볼륨하이버튼이 나오게끔 세팅. 탭할 시 _onMuteTap 함수가 작동하도록 세팅.

GestureDetector(
                  onTap: _onMuteTap,
                  child: _isMuted
                      ? const FaIcon(
                          FontAwesomeIcons.volumeXmark,
                          color: Colors.black54,
                        )
                      : const FaIcon(
                          FontAwesomeIcons.volumeHigh,
                          color: Colors.black54,
                        ),
                ),


2. _isMuted 플래그는 웹일 경우에는 볼륨 제로로 시작하기 때문에 kIsWeb일 경우에는 isMuted=true로, 아닐 경우에는 false로 세팅되도록 작성

bool _isMuted = kIsWeb ? true : false;


3. _onMuteTap 함수는 음소거가 아닌 상태에서 누르면, 음소거가 되게끔, 음소거인 상태에서 누르면 시스템 볼륨값을 가져오도록 세팅(setVideoPlayerVolume 메소드가 해당 부분)

void _onMuteTap() {
    setState(() {
      if (!_isMuted) {
        _videoPlayerController.setVolume(0);
      } else {
        _setVideoPlayerVolume();
      }
      _isMuted = !_isMuted;
    });
  }



4. setVideoPlayerVolume 메소드의 경우 volume_controller 패키지를 임포트해서 시스템 볼륨을 가져온 뒤, Video Player의 볼륨값을 시스템 볼륨값으로 세팅하는 메소드

void _setVideoPlayerVolume() async {
    _systemVolume = await VolumeController().getVolume();
    _videoPlayerController.setVolume(_systemVolume);
  }


특정 위젯의 높이나 너비값을 유동적으로 변하게 하고 싶을 때?
LayoutBuilder를 사용하면 됨. 
LayoutBuilder 위젯은 어느 위치에 두느냐에 따라 최대, 최소값이 달라질 수 있음! 가령 높이와 너비가 세팅된 SizedBox 내부에 들어가면 제대로 반영하지 못할 수 있음!
(코드 작성은 아래 코드 참고)

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

  @override
  Widget build(BuildContext context) {
    final size = MediaQuery.of(context).size;
    return Scaffold(
      body: LayoutBuilder(
        builder: (context, constraints) => Container(
          width: constraints.maxWidth,
          height: constraints.maxHeight,
          color: Colors.teal,
          child: Center(
            child: Text(
              "${size.width} / ${constraints.maxWidth}",
              style: const TextStyle(
                color: Colors.white,
                fontSize: 98,
              ),
            ),
          ),
        ),
      ),
    );
  }
}


Container 위젯 내부에 constraints 속성이 자체적으로 있기 때문에, 여기서 높이나 너비의 최대, 최소값을 세팅할 수 있음!

 

 

출처

노마드 코더 유료 강의

(광고 아니고 ㄹㅇ 강추!)