๐Ÿ“š Today I Learned 12ํšŒ์ฐจ - Flutter ๋””์ง€ํ„ธ ์ฃผ์‚ฌ์œ„ ์•ฑ ๋งŒ๋“ค๊ธฐ ๐ŸŽฒ

3 ๋ถ„ ์†Œ์š”

๐Ÿ“– Flutter ๋””์ง€ํ„ธ ์ฃผ์‚ฌ์œ„ ์•ฑ ๋งŒ๋“ค๊ธฐ

๐ŸŽฏ ํ•™์Šต ๋ชฉํ‘œ

  • ๋””์ง€ํ„ธ ์ฃผ์‚ฌ์œ„ ์•ฑ ๋งŒ๋“ค๊ธฐ
  • TabController์™€ TickerProviderStateMixin ํ™œ์šฉ๋ฒ• ์ตํžˆ๊ธฐ
  • ๊ฐ€์†๋„๊ณ„์™€ ์ž์ด๋กœ์Šค์ฝ”ํ”„ ์›๋ฆฌ ์ดํ•ดํ•˜๊ธฐ
  • ํš๋“ค๋ฆผ ๊ฐ์ง€ ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ

๐Ÿ“š ์ฃผ์š” ๋‚ด์šฉ

โญ ํ”„๋กœ์ ํŠธ ๊ตฌ์„ฑ ์š”๊ตฌ์‚ฌํ•ญ

  • ๊ฐ€์†๋„๊ณ„๋Š” ๊ฐ€์†๋„๋ฅผ ์ธก์ •ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์–ด๋А ์ •๋„์˜ ๊ฐ€์† ์ˆ˜์น˜๋ฅผ ํ”๋“œ๋Š” ํ–‰๋™์œผ๋กœ ์ธ์‹ํ• ์ง€์— ๋Œ€ํ•œ ๊ธฐ์ค€์ด ์ค‘์š”
  • ์‚ฌ์šฉ์ž๊ฐ€ ํŠน์ • ์ˆ˜์น˜๋ฅผ ๋„˜๋Š” ๊ฐ•๋„๋กœ ํ•ธ๋“œํฐ์„ ํ”๋“  ์ˆœ๊ฐ„์„ ์ธ์‹ํ•  ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„
  • ๊ทธ ๊ธฐ์ค€์„ Slider ์œ„์ ฏ์„ ํ†ตํ•ด ์„ค์ •
  • ํ™”๋ฉด 2๊ฐœ๋ฅผ ๋งŒ๋“ค์–ด ํƒญ๊ณผ ์Šคํฌ๋กค๋กœ ์ด๋™(๋‘ ํ™”๋ฉด์„ ๊ฐ๊ฐ ๋”ฐ๋กœ ์œ„์ ฏ์œผ๋กœ ๊ตฌํ˜„ํ•˜๊ณ  BottomNavigationBar๋ฅผ ์ด์šฉ)

๐Ÿ’ช ์‚ฌ์ „ ์ง€์‹

๊ฐ€์†๋„๊ณ„

๊ฐ€์†๋„๊ณ„๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด x,y,z ์ถ•์˜ ์ธก์ • ๊ฒฐ๊ณผ๋ฅผ ๋ชจ๋‘ double ๊ฐ’์œผ๋กœ ๋ฐ˜ํ™˜

์ž์ด๋กœ์Šค์ฝ”ํ”„

๊ฐ€์†๋„๊ณ„๋Š” x,y,z ์ถ•์˜ ์ง์„  ์›€์ง์ž„๋งŒ ์ธก์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ž์ด๋กœ์Šค์ฝ”ํ”„๋Š” ์ด ๋‹จ์ ์„ ๋ณด์™„ํ•ด์„œ x,u,z ์ถ•์˜ ํšŒ์ „์„ ์ธก์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

x: ์ขŒ์šฐ๋กœ ํšŒ์ „ํ•˜๋Š” ๋ฐฉํ–ฅ y: ์œ„์•„๋ž˜๋กœ ํšŒ์ „ํ•˜๋Š” ๋ฐฉํ–ฅ z: ์•ž๋’ค๋กœ ํšŒ์ „ํ•˜๋Š” ๋ฐฉํ–ฅ

โšก ํ•ต์‹ฌ ๊ตฌํ˜„ ์ง€์‹

  • TabController์—์„œ vsync ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ํ•„์ˆ˜๋กœ TickerProviderStateMixin์„ ์‚ฌ์šฉํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.
  • TickerProviderStateMixin๊ณผ SingleTickerProviderMixin์€ ์• ๋‹ˆ๋ฉ”์ด์…˜์˜ ํšจ์œจ์„ ์˜ฌ๋ ค์ฃผ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
  • vsync๋Š” TickerProviderStateMixin์„ ์‚ฌ์šฉํ•˜๋Š” state ํด๋ž˜์Šค๋ฅผ this ํ˜•ํƒœ๋กœ ๋„ฃ์–ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
  • ๊ทธ๋Ÿฌ๋ฉด controller๋ฅผ ์ด์šฉํ•ด์„œ TabBarView๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

TickerProviderStateMixin ์‹ฌํ™” ์ดํ•ด

๐Ÿ’ก ํ•ต์‹ฌ: TickerProviderStateMixin์€ โ€œ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์œ„ํ•œ ์‹ฌ์žฅ ๋ฐ•๋™๊ธฐโ€๋ผ๊ณ  ์ƒ๊ฐํ•˜๋ฉด ์ดํ•ดํ•˜๊ธฐ ์‰ฝ์Šต๋‹ˆ๋‹ค.

๐Ÿ“ฑ Ticker: ์• ๋‹ˆ๋ฉ”์ด์…˜์˜ ์‹ฌ์žฅ ๋ฐ•๋™

์• ๋‹ˆ๋ฉ”์ด์…˜์€ ๋ณธ์งˆ์ ์œผ๋กœ ์•„์ฃผ ์งง์€ ์‹œ๊ฐ„ ๋™์•ˆ ํ™”๋ฉด์„ ๊ณ„์†ํ•ด์„œ ๋‹ค์‹œ ๊ทธ๋ฆฌ๋Š” ์ž‘์—…์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, 1์ดˆ ๋™์•ˆ ์–ด๋–ค ์œ„์ ฏ์ด ์ ์  ์ปค์ง€๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์€ ์‚ฌ์‹ค 1์ดˆ ๋™์•ˆ ์•ฝ 60๋ฒˆ(60fps ๊ธฐ์ค€)์— ๊ฑธ์ณ ์œ„์ ฏ์˜ ํฌ๊ธฐ๋ฅผ ๋ฏธ์„ธํ•˜๊ฒŒ ํ‚ค์šฐ๋ฉด์„œ ํ™”๋ฉด์„ ์ƒˆ๋กœ๊ณ ์นจํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ด๋•Œ, โ€œ์ง€๊ธˆ ๋‹ค์‹œ ๊ทธ๋ ค!โ€, โ€œ๋˜ ๋‹ค์‹œ ๊ทธ๋ ค!โ€์™€ ๊ฐ™์ด ์ผ์ •ํ•œ ๊ฐ„๊ฒฉ์œผ๋กœ ์‹ ํ˜ธ๋ฅผ ๋ณด๋‚ด์ฃผ๋Š” ์—ญํ• ์„ ํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ Ticker์ž…๋‹ˆ๋‹ค.

๐Ÿ” TickerProviderStateMixin ์ด๋ฆ„ ๋ถ„์„

๊ตฌ์„ฑ ์š”์†Œ ์—ญํ•  ์„ค๋ช…
Ticker ์‹ฌ์žฅ ๋ฐ•๋™ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํ”„๋ ˆ์ž„๋งˆ๋‹ค ์‹ ํ˜ธ๋ฅผ ์ฃผ๋Š” ์‹ฌ์žฅ ๋ฐ•๋™
Provider ์ œ๊ณต์ž Ticker๋ฅผ AnimationController์—๊ฒŒ ์ œ๊ณตํ•˜๋Š” ์—ญํ• 
State ์ƒํƒœ ๊ด€๋ฆฌ StatefulWidget์˜ ์ƒ๋ช…์ฃผ๊ธฐ์™€ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋™๊ธฐํ™”
Mixin ๊ธฐ๋Šฅ ์ถ”๊ฐ€ with ํ‚ค์›Œ๋“œ๋กœ State ํด๋ž˜์Šค์— ๊ธฐ๋Šฅ ์žฅ์ฐฉ

โšก ํšจ์œจ์„ฑ์˜ ํ•ต์‹ฌ: vsync

  • ๋ฌธ์ œ ์ƒํ™ฉ: Ticker๊ฐ€ ํ™”๋ฉด ์ƒํƒœ์™€ ์ƒ๊ด€์—†์ด ๊ณ„์† ์‹ ํ˜ธ๋ฅผ ๋ณด๋‚ด๋ฉด CPU์™€ ๋ฐฐํ„ฐ๋ฆฌ ๋‚ญ๋น„ ๐Ÿ”‹
  • ํ•ด๊ฒฐ์ฑ…: vsync(Vertical Synchronization)๋ฅผ ํ†ตํ•ด ํ™”๋ฉด ์ฃผ์‚ฌ์œจ์— ๋งž์ถฐ ๋™๊ธฐํ™”
  • ํšจ๊ณผ: ํ™”๋ฉด์ด ๋ณด์ผ ๋•Œ๋งŒ ์ž‘๋™ํ•˜๊ณ , ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ „ํ™˜ ์‹œ ์ž๋™์œผ๋กœ ๋ฉˆ์ถค

  • initState์—์„œ TabController์˜ Listener๋ฅผ ๋“ฑ๋กํ•ด์„œ controller์˜ ์†์„ฑ์ด ๋ณ€๊ฒฝ๋  ๋•Œ ๋งˆ๋‹ค setState๋ฅผ ์‹คํ–‰ํ•ด์„œ ํ™”๋ฉด์„ ๋‹ค์‹œ ๊ทธ๋ฆด ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ป ์‹ค์Šต ๋ฐ ์˜ˆ์ œ

๐Ÿ”ง ์‹ค์ œ ์ž‘์„ฑ ์ฝ”๋“œ

import 'package:flutter/material.dart';
import 'package:random_dice/const/colors.dart';
import 'package:random_dice/screen/home_screen.dart';
import 'package:random_dice/screen/root_screen.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData(
        scaffoldBackgroundColor: backgroundColor,
        sliderTheme: SliderThemeData(
          thumbColor: primaryColor,
          activeTickMarkColor: primaryColor,
          inactiveTickMarkColor: primaryColor.withOpacity(0.3),
        ),
        bottomNavigationBarTheme: BottomNavigationBarThemeData(
          selectedItemColor: primaryColor,
          unselectedItemColor: secondaryColor,
          backgroundColor: backgroundColor,
        ),
      ),
      home: RootScreen(),
    ),
  );
}

import 'dart:math';

import 'package:flutter/material.dart';
import 'package:random_dice/screen/home_screen.dart';
import 'package:random_dice/screen/settings_screen.dart';
import 'package:shake/shake.dart';

class RootScreen extends StatefulWidget {
  const RootScreen({super.key});

  @override
  State<RootScreen> createState() => _RootScreenState();
}

class _RootScreenState extends State<RootScreen> with TickerProviderStateMixin {
  TabController? controller;
  double threshold = 2.7;
  int number = 1;
  ShakeDetector? shakeDetector;

  @override
  void initState() {
    super.initState();
    controller = TabController(length: 2, vsync: this);
    controller!.addListener(tabListener);

    shakeDetector = ShakeDetector.autoStart(
      // ํ”๋“ค๋ฆผ ๊ฐ์ง€ ์‹œ์ž‘
      shakeSlopTimeMS: 100, // ๊ฐ์ง€ ์ฃผ๊ธฐ
      shakeThresholdGravity: threshold,
      onPhoneShake: onPhoneShake,
    );
  }

  void onPhoneShake() {
    final rand = Random();
    setState(() {
      number = rand.nextInt(5) + 1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TabBarView(controller: controller, children: renderChildren()),
      bottomNavigationBar: renderBottomNavigation(),
    );
  }

  @override
  void dispose() {
    controller!.removeListener(tabListener);
    shakeDetector!.stopListening();
    super.dispose();
  }

  void tabListener() {
    setState(() {});
  }

  void onThresholdChange(double value) {
    setState(() {
      threshold = value;
    });
  }

  List<Widget> renderChildren() {
    return [
      HomeScreen(number: number),
      SettingsScreen(
        threshold: threshold,
        onThresholdChange: onThresholdChange,
      ),
    ];
  }

  BottomNavigationBar renderBottomNavigation() {
    return BottomNavigationBar(
      currentIndex: controller!.index,
      onTap: (index) {
        setState(() {
          controller!.animateTo(index);
        });
      },
      items: [
        BottomNavigationBarItem(
          icon: Icon(Icons.edgesensor_high_outlined),
          label: '์ฃผ์‚ฌ์œ„',
        ),
        BottomNavigationBarItem(icon: Icon(Icons.settings), label: '์„ค์ •'),
      ],
    );
  }
}

import 'package:flutter/material.dart';
import 'package:random_dice/const/colors.dart';

class HomeScreen extends StatelessWidget {
  final int number;

  const HomeScreen({super.key, required this.number});

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Center(child: Image.asset('asset/img/$number.png')),
        SizedBox(height: 32),
        Text(
          'ํ–‰์šด์˜ ์ˆซ์ž',
          style: TextStyle(
            color: secondaryColor,
            fontSize: 20.0,
            fontWeight: FontWeight.w700,
          ),
        ),
        SizedBox(height: 12.0),
        Text(
          number.toString(),
          style: TextStyle(
            color: primaryColor,
            fontSize: 60.0,
            fontWeight: FontWeight.w200,
          ),
        ),
      ],
    );
  }
}

๐Ÿ“ฑ ๊ตฌํ˜„ ๊ฒฐ๊ณผ

์ฃผ์‚ฌ์œ„ ํ™”๋ฉด

๋ฉ”์ธ ์ฃผ์‚ฌ์œ„ ํ™”๋ฉด

์„ค์ • ํ™”๋ฉด

ํ”๋“ค๋ฆผ ๊ฐ๋„ ์„ค์ • ํ™”๋ฉด

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ

โœ… ์˜ค๋Š˜ ๋ฐฐ์šด ๊ฒƒ

  • TabController์™€ TickerProviderStateMixin: ํƒญ ๋„ค๋น„๊ฒŒ์ด์…˜๊ณผ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ์œจ์„ฑ ๊ด€๋ฆฌ
  • ๊ฐ€์†๋„๊ณ„์™€ ์ž์ด๋กœ์Šค์ฝ”ํ”„: ์„ผ์„œ ๋ฐ์ดํ„ฐ๋ฅผ ํ™œ์šฉํ•œ ํ”๋“ค๋ฆผ ๊ฐ์ง€ ์›๋ฆฌ
  • ShakeDetector: ํ”๋“ค๋ฆผ ๊ฐ์ง€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ™œ์šฉ๋ฒ•
  • Bottom Navigation: ํƒญ ๋ฐ”๋ฅผ ํ†ตํ•œ ํ™”๋ฉด ์ „ํ™˜ ๊ตฌํ˜„
  • Slider Widget: ์‚ฌ์šฉ์ž ์„ค์ •๊ฐ’ ์กฐ์ • UI ๊ตฌํ˜„
  • ์ƒํƒœ ๊ด€๋ฆฌ: ์—ฌ๋Ÿฌ ํ™”๋ฉด ๊ฐ„ ๋ฐ์ดํ„ฐ ๊ณต์œ  ๋ฐ ์ƒํƒœ ๋™๊ธฐํ™”

๐Ÿš€ ๋‹ค์Œ ๊ณ„ํš

  • ๊ณ ๊ธ‰ ์„ผ์„œ ํ™œ์šฉ: ๋” ์ •๊ตํ•œ ์ œ์Šค์ฒ˜ ์ธ์‹ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
  • ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ฐ•ํ™”: ์ฃผ์‚ฌ์œ„ ๊ตด๋ฆฌ๊ธฐ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ ๊ตฌํ˜„
  • ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„ : ํ–…ํ‹ฑ ํ”ผ๋“œ๋ฐฑ๊ณผ ์‚ฌ์šด๋“œ ํšจ๊ณผ ์ถ”๊ฐ€
  • ๋ฐ์ดํ„ฐ ์ €์žฅ: SharedPreferences๋ฅผ ํ™œ์šฉํ•œ ์„ค์ •๊ฐ’ ์˜๊ตฌ ์ €์žฅ

๐Ÿ’ก ํ•ต์‹ฌ: Flutter์—์„œ ์„ผ์„œ์™€ ํƒญ ๋„ค๋น„๊ฒŒ์ด์…˜์„ ๊ฒฐํ•ฉํ•˜์—ฌ ์‹ค์šฉ์ ์ธ ์•ฑ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. TickerProviderStateMixin์˜ ์ค‘์š”์„ฑ๊ณผ ํšจ์œจ์ ์ธ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ด€๋ฆฌ๋ฒ•์„ ์ฒด๋“ํ–ˆ์Šต๋‹ˆ๋‹ค! ๐ŸŽฒ

๋Œ“๊ธ€๋‚จ๊ธฐ๊ธฐ