๐Ÿงˆ Butter & Jam

[Flutter] ์›น๋ทฐ ๊ตฌํ˜„(๋„ค์ด๋ฒ„)

BreadDev 2025. 7. 2. 17:42
728x90

๊ฐ•์˜์—์„œ ๋ฐฐ์šด ๊ธฐ๋ณธ ์ฝ”๋“œ๋ฅผ ๊ฐ€์ง€๊ณ  ์ถ”๊ฐ€๋กœ ๊ตฌํ˜„ํ•ด์„œ ๋„ค์ด๋ฒ„ ํ™”๋ฉด์˜ ์›น๋ทฐ๋ฅผ ๊ตฌํ˜„ํ•ด๋ณด์•˜๋‹ค.

// ๊ธฐ๋ณธ์ ์ธ StatelessWidget ๊ตฌ์กฐ
class HomeScreen extends StatelessWidget {
  WebViewController controller = WebViewController()
    ..setJavaScriptMode(JavaScriptMode.unrestricted)
    ..loadRequest(homeUrl);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(...),
      body: WebViewWidget(controller: controller),
    );
  }
}

 

 


1๋‹จ๊ณ„: StatefulWidget์œผ๋กœ ๋ณ€๊ฒฝ

์™œ ๋ณ€๊ฒฝํ–ˆ๋‚˜?

  • ์›นํŽ˜์ด์ง€ ๋กœ๋”ฉ ์ƒํƒœ ์ถ”์  ํ•„์š”
  • ๋’ค๋กœ๊ฐ€๊ธฐ/์•ž์œผ๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ ์ƒํƒœ ๊ด€๋ฆฌ
  • ๋™์  UI ์—…๋ฐ์ดํŠธ

ํ•ต์‹ฌ ๋ณ€๊ฒฝ์‚ฌํ•ญ

class HomeScreen extends StatefulWidget {
  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  bool isLoading = true;
  bool canGoBack = false;
  bool canGoForward = false;
  String currentUrl = '';
}

2๋‹จ๊ณ„: ํŒŒ์ผ ๊ตฌ์กฐํ™”

๊ธฐ์กด: ๋‹จ์ผ ํŒŒ์ผ

lib/
โ””โ”€โ”€ main.dart (๋ชจ๋“  ์ฝ”๋“œ)

๊ฐœ์„ : ๊ธฐ๋Šฅ๋ณ„ ๋ถ„๋ฆฌ

lib/
โ”œโ”€โ”€ main.dart                    # ์•ฑ ์ง„์ž…์ 
โ”œโ”€โ”€ constants/app_constants.dart # ์ƒ์ˆ˜ ๊ด€๋ฆฌ
โ”œโ”€โ”€ screens/home_screen.dart     # ๋ฉ”์ธ ํ™”๋ฉด
โ””โ”€โ”€ widgets/
    โ”œโ”€โ”€ loading_widget.dart      # ๋กœ๋”ฉ ์ปดํฌ๋„ŒํŠธ
    โ””โ”€โ”€ bottom_navigation_widget.dart # ํ•˜๋‹จ ๋„ค๋น„๊ฒŒ์ด์…˜
  • ์ฝ”๋“œ ์žฌ์‚ฌ์šฉ์„ฑ ์ฆ๊ฐ€
  • ์œ ์ง€๋ณด์ˆ˜ ํŽธ์˜์„ฑ
  • ๊ฐ ํŒŒ์ผ์˜ ์ฑ…์ž„ ๋ช…ํ™•ํ™”

3๋‹จ๊ณ„: UI/UX ๊ฐœ์„ 

๋„ค์ด๋ฒ„ ์ƒ‰์ƒ ์ ์šฉ

// constants/app_constants.dart
static const Color naverGreen = Color(0xFF03C75A);

๋กœ๋”ฉ ์ƒํƒœ ๊ด€๋ฆฌ

// NavigationDelegate๋กœ ์›นํŽ˜์ด์ง€ ์ƒํƒœ ์ถ”์ 
NavigationDelegate(
  onPageStarted: (String url) {
    setState(() {
      isLoading = true;
      currentUrl = url;
    });
  },
  onPageFinished: (String url) async {
    setState(() {
      isLoading = false;
    });
    await _updateNavigationState();
  },
)

๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฒ„ํŠผ ์ƒํƒœ ๊ด€๋ฆฌ

Future<void> _updateNavigationState() async {
  final backStatus = await controller.canGoBack();
  final forwardStatus = await controller.canGoForward();
  setState(() {
    canGoBack = backStatus;
    canGoForward = forwardStatus;
  });
}

4๋‹จ๊ณ„: ๋„ค์ด๋ฒ„ ์•ฑ ๋А๋‚Œ ์ถ”๊ฐ€

ํ–…ํ‹ฑ ํ”ผ๋“œ๋ฐฑ

import 'package:flutter/services.dart';

void _refresh() {
  controller.reload();
  HapticFeedback.lightImpact(); // ํ„ฐ์น˜ ํ”ผ๋“œ๋ฐฑ
}

ํ•˜๋‹จ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ” ๊ตฌํ˜„

// ๋„ค์ด๋ฒ„ ์•ฑ๊ณผ ๋™์ผํ•œ ๊ตฌ์„ฑ
Row(
  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  children: [
    _buildBottomNavButton(icon: Icons.home, label: 'ํ™ˆ'),
    _buildBottomNavButton(icon: Icons.search, label: '๊ฒ€์ƒ‰'),
    _buildBottomNavButton(icon: Icons.newspaper, label: '๋‰ด์Šค'),
    _buildBottomNavButton(icon: Icons.shopping_cart, label: '์‡ผํ•‘'),
  ],
)

5๋‹จ๊ณ„: ๋ ˆ์ด์•„์›ƒ ์„ธ๋ถ€ ์กฐ์ •

AppBar ์ •๋ ฌ ๋ณ€๊ฒฝ

AppBar(
  centerTitle: false, // ๊ธฐ๋ณธ true์—์„œ false๋กœ ๋ณ€๊ฒฝ
  title: Row(
    children: [
      Icon(Icons.language),
      Text('๋„ค์ด๋ฒ„์›น๋ทฐ'), // ํ•œ๊ธ€๋กœ ๋ณ€๊ฒฝ
    ],
  ),
)

SafeArea ์ ์šฉ + ์ƒ‰์ƒ ํ†ต์ผ

// ์ฒ˜์Œ ์‹œ๋„ (๋ฌธ์ œ ๋ฐœ์ƒ)
SafeArea(child: Container(...)) // ํ•˜๋‹จ ํฐ์ƒ‰ ์—ฌ๋ฐฑ ์ƒ๊น€

// ์ตœ์ข… ํ•ด๊ฒฐ
Container(
  color: AppConstants.naverGreen, // ์ „์ฒด ์˜์—ญ ์ƒ‰์ƒ
  child: SafeArea(
    child: Container(...), // ์•ˆ์ „ํ•œ ์˜์—ญ์—๋งŒ ์ปจํ…์ธ 
  ),
)

BoxShadow ์ œ๊ฑฐ

// ๊ธฐ์กด: decoration ์‚ฌ์šฉ
decoration: BoxDecoration(
  color: AppConstants.naverGreen,
  boxShadow: [...], // ์›์น˜ ์•Š๋Š” ์„  ์ƒ์„ฑ
)

// ๊ฐœ์„ : ๋‹จ์ˆœ color ์‚ฌ์šฉ
color: AppConstants.naverGreen, // ๊น”๋”ํ•œ ๋‹จ์ƒ‰

 

๊นจ๋‹ฌ์€์ 

1. ์ƒํƒœ ๊ด€๋ฆฌ์˜ ์ค‘์š”์„ฑ

  • StatelessWidget → StatefulWidget ๋ณ€๊ฒฝ์œผ๋กœ ๋™์  UI ๊ตฌํ˜„
  • setState()๋ฅผ ํ†ตํ•œ ์‹ค์‹œ๊ฐ„ ์ƒํƒœ ์—…๋ฐ์ดํŠธ

2. WebView ์ œ์–ด

// ๊ธฐ๋ณธ ์„ค์ •
WebViewController()
  ..setJavaScriptMode(JavaScriptMode.unrestricted)
  ..setNavigationDelegate(NavigationDelegate(...))
  ..loadRequest(Uri.parse(url))

// ๋„ค๋น„๊ฒŒ์ด์…˜ ์ œ์–ด
await controller.canGoBack()
await controller.goBack()
controller.reload()

3. ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ์˜ ์žฅ์ 

  • ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์œ„์ ฏ ์ƒ์„ฑ
  • ์ฝ”๋“œ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ

4. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„ 

  • ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ
  • ํ–…ํ‹ฑ ํ”ผ๋“œ๋ฐฑ
  • ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋ฐ ์‚ฌ์šฉ์ž ์•Œ๋ฆผ

5. ๋ ˆ์ด์•„์›ƒ ์„ธ๋ถ€ ์กฐ์ •

  • SafeArea ํ™œ์šฉ๋ฒ•
  • ์ƒ‰์ƒ ํ†ต์ผ์„ฑ ์œ ์ง€
  • AppBar ์ปค์Šคํ„ฐ๋งˆ์ด์ง•

์ตœ์ข… ๊ฒฐ๊ณผ

์™„์„ฑ๋œ ๊ธฐ๋Šฅ๋“ค:

โœ… ๋„ค์ด๋ฒ„ ๋ธŒ๋žœ๋“œ ์ปฌ๋Ÿฌ ์ ์šฉ
โœ… ๋กœ๋”ฉ ์ƒํƒœ ํ‘œ์‹œ
โœ… ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฒ„ํŠผ (๋’ค๋กœ๊ฐ€๊ธฐ, ์•ž์œผ๋กœ๊ฐ€๊ธฐ, ์ƒˆ๋กœ๊ณ ์นจ, ํ™ˆ)
โœ… ํ•˜๋‹จ ๋„ค๋น„๊ฒŒ์ด์…˜ ๋ฐ” (ํ™ˆ, ๊ฒ€์ƒ‰, ๋‰ด์Šค, ์‡ผํ•‘)
โœ… ํ–…ํ‹ฑ ํ”ผ๋“œ๋ฐฑ
โœ… ๋งํฌ ๋ณต์‚ฌ/๊ณต์œ  ๊ธฐ๋Šฅ
โœ… SafeArea ์ ์šฉ
โœ… ์—๋Ÿฌ ์ฒ˜๋ฆฌ


๐Ÿ” ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ… ๊ธฐ๋ก

๋ฌธ์ œ 1: ํ•˜๋‹จ SafeArea ํฐ์ƒ‰ ์—ฌ๋ฐฑ

ํ•ด๊ฒฐ: Container๋กœ ์ „์ฒด ์˜์—ญ ์ƒ‰์ƒ ์ง€์ • ํ›„ SafeArea ์ ์šฉ

๋ฌธ์ œ 2: BoxShadow๋กœ ์ธํ•œ ์›์น˜ ์•Š๋Š” ์„ 

ํ•ด๊ฒฐ: BoxDecoration ์ œ๊ฑฐํ•˜๊ณ  ๋‹จ์ˆœ color ์†์„ฑ ์‚ฌ์šฉ

๋ฌธ์ œ 3: ์ƒํƒœ ๊ด€๋ฆฌ ๋ถ€์กฑ

ํ•ด๊ฒฐ: StatefulWidget ์ „ํ™˜ + NavigationDelegate ํ™œ์šฉ