๊ฐ์
์ผ๋ง ์ ์ ํ๋ฌํฐ๋ก ๋๊ธ๋๊ธํ UI๋ฅผ ๊ฐ๋ฐํ๋ ์ค Stack ๊ตฌ์กฐ์ ์์์์ ฏ๋ค์ ์ํ์ผ๋ก ๋ฐฐ์นํด์ผ ๋๋ ์ผ์ด ์๊ฒผ๋ค.
๊ฐ์ฅ ํจ์จ์ ์ธ ์ํ๋ฐฐ์น๋ฅผ ํ๊ธฐ ์ํด์๋ dart:math ํจํค์ง์ ์๋ ์ผ๊ฐํจ์ ๊ธฐ๋ฅ๋ค (sin, cos) ๋ฑ์ ์ฌ์ฉํ ํ์๊ฐ ์์๊ณ , ๊น๋ํ ๋ฐฉ๋ฒ์ผ๋ก ๊ตฌํ์ ํ๊ฒ ๋์ด์ ๊ณต์ ๋ฅผ ํ๊ณ ์ถ์๋ค.
์๋์ ์ฌ์ง์ ๋ด๊ฐ ๊ตฌํํ ์ํ ์คํ ๊ตฌ์กฐ์ ๋ชจ์ต์ด๋ค.
๊ณผ์ผ์ ์ด๋ฆ๋ค์ ๋ฐ์ ๊ฐ ๊ฐ์๋งํผ ์๋์ผ๋ก ์ํ ๋ฐฐ์น๊ฐ ์ด๋ค์ง๋ ๊ตฌ์กฐ๋ค.
์ด๋ฅผ ๊ตฌํํ๊ธฐ ์ํด์๋ ์ฐ์ ์ผ๊ฐ๋น์ ๋ํด ์ดํดํ ํ์๊ฐ ์๋ค.
์คํ๊ต ๋ ๋ฐฐ์ ๋ ๋ด์ฉ์ด์ง๋ง ์๊ฐ์ด ์ง๋๋ฉด์ ๊ฑฐ์ ๊ธฐ์ต์ด ๋์ง ์์๋๋ฐ, ์ํ ์ขํ๋ฅผ ๊ตฌํํ๊ธฐ ์ํด ์ ๋ฐฉ์ ์์ ์ด์ฉํ๋ค ๋ ์ฌ์ด ๋ฐฉ๋ฒ์ธ ์ผ๊ฐ๋น๋ฅผ ๊ธฐ์ตํด ๋ด๊ฒ ๋์๋ค.
์ผ๊ฐ๋น๋ฅผ ํตํ ๊ณ์ฐ ๊ณต์
์, ๋ณต์ต์ ํด๋ณด์.
์ฌ๊ธฐ์ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ ๊ฒ์ sin, cos์ด๋ค
Stack์ Positioined๋ ํก์ฌ x, y ์ขํ๊ณ๋ฅผ ๋ณด๋ ๊ฒ ๊ฐ๋ค.
์ด๋ฅผ ํตํด ์ฐ๋ฆฌ๊ฐ ์๊ณ ์๋ ๊ฐ์ธ ๋น๋ณ์ ๊ธธ์ด (์์ ๋ฐ์ง๋ฆ)์ ์ด์ฉํด์
x ๊ฐ๊ณผ y๊ฐ์ ๊ตฌํ ์ ์๋ค.
์์ ๋ฐ์ง๋ฆ์ธ distance = c
x = b
y = a
๋ผ๊ณ ๊ฐ์ ํ ๋,
x = distance * cos(A)
y = distance * sin(A)
๊ฐ ๋๋ค.
์ฌ๊ธฐ์ ์ฐ๋ฆฌ๋ A ๊ฐ๋ ๊ตฌํด์ผ ํ๋๋ฐ ์ํ์์ A๋ radian์ด๋ผ๋ ๋จ์๋ฅผ ์ฌ์ฉํ๋ค.
ํ์ง๋ง ์ฐ๋ฆฌ๋ radian ๋ณด๋จ ๊ฐ๋(degree)๋ฅผ ์ด์ฉํ๋ ๊ฒ ๊ณ์ฐํ๊ธฐ ๋ ์ฝ๊ธฐ ๋๋ฌธ์ ํด๋น ๊ฐ๋ ๊ตฌํ๋ ๊ณต์์ ์ฐพ์๋ด์ผ ํ๋ค.
๊ฐ๋(degree)๋ฅผ ํตํด ์ป๊ณ ์ ํ๋ radian์ ๊ณต์์ ๋ค์๊ณผ ๊ฐ๋ค.
1 ๋ผ๋์ = (pi/180) * degree
๊ทธ๋ ๋ค๋ฉด ์์ค์ฌ๊ณผ์ ๊ฑฐ๋ฆฌ๊ฐ 10์ด๊ณ ๊ฐ๋๊ฐ 30๋์ผ ๋ x, y ์ขํ๋ฅผ ๊ตฌํ๋ ๊ณต์์ ๋ค์๊ณผ ๊ฐ๋ค
x = 10 * cos( (3.14/180) * 30 )
y = 10 * sin( (3.14/180) * 30 )
x = 9.99...
y = 0.09...
์ฝ๋๋ก ๋ณํ
์ด์ ๊ณต์์ ํตํด ์ฝ๋๋ฅผ ๊ตฌํํด ๋ณด์
์๋์ ์ฝ๋๋ ๋ผ๋์ ๊ฐ์ผ๋ก ๋ณํํ๊ธฐ ์ํ ๋ฉ์๋์ด๋ค.
//๋ผ๋์๊ฐ์ผ๋ก ๋ณํํ๋ ๋ฉ์๋
double getRadian(double degree) {
return (pi / 180) * degree;
}
๊ทธ๋ฆฌ๊ณ ์๋์ ์ฝ๋๋ ๊ฐ๋์ ๊ฑฐ๋ฆฌ์ ๋ฐ๋ฅธ x, y ์ขํ๋ฅผ ๊ตฌํ๋ ๋ฉ์๋์ด๋ค.
//๊ฐ๋์ ๊ฑฐ๋ฆฌ์ ๋ฐ๋ฅธ x,y ์ขํ๊ฐ ๊ตฌํ๊ธฐ
Offset getPosition({required double degree, required double distance}) {
final double radian = getRadian(degree); //๊ฐ๋๋ฅผ ๋ผ๋์ ๋จ์๋ก ๋ณ๊ฒฝ
final double dx = distance * cos(radian);
final double dy = distance * sin(radian);
return Offset(dx, dy);
}
๊ทธ๋ฌ๋ฉด ๋๋ ์ด๋ค ์์ผ๋ก Stack์ Positioned๋ฅผ ์ด์ฉํ์ฌ ์ํ์ผ๋ก ๋ฐฐ์น๋ฅผ ํ์๊น?
์๋์ ์ ์ฒด์ฝ๋๋ฅผ ํตํด ์ดํด๋ณผ ์ ์๋ค.
์ ์ฒด ์ฝ๋ <main.dart>
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Cirlcle Stack Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Cirlcle Stack Example'),
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({super.key, required this.title});
final String title;
final double stackSize = 700;
final double childSize = 60;
final List<String> fruits = [
'์ฌ๊ณผ',
'๋ฐ๋๋',
'๋ธ๊ธฐ',
'ํฌ๋',
'์๋ฐ',
'๋ฉ๋ก ',
'์ค๋ ์ง',
'๊ฐ',
'๋ ๋ชฌ',
'์ฐธ์ธ',
'๋ฐฐ',
];
@override
Widget build(BuildContext context) {
//์์์์ ฏ์ ๊ธฐ์ค์ ์ด ์ข์ธก ์๋จ์ด๊ธฐ ๋๋ฌธ์
//์ค์ฌ์ ์ ์์น๋ ์คํ์ ์ฌ์ด์ฆ/2๋ฅผ ํ ๊ฐ์ ์์์์ ฏ์ ์ฌ์ด์ฆ/2 ๋ฅผ ๋บ๋ค
final Offset centerOffset = Offset(stackSize / 2 - (childSize / 2), stackSize / 2 - (childSize / 2));
const double distance = 250; //์ค์ฌ์ ์์ ๋จ์ด์ง ๊ฑฐ๋ฆฌ
final double degreeInterval = 360 / fruits.length; //์์ ๊ฐ๋ 360๋๋ฅผ ๊ณผ์ผ๋ค์ ๊ฐ์๋งํผ ๋๋ ์ ๊ฐ๊ฒฉ ๊ตฌํ๊ธฐ
final List<Widget> fruitWidgets = [];
for (int i = 0; i < fruits.length; i++) {
final double degree = (i * degreeInterval) + 90; //12์ ๋ฐฉํฅ์์ ์์ํ๊ธฐ ์ํด ๊ฐ๋ 90๋๋ฅผ ๋ํจ
final Offset position = getPosition(degree: degree, distance: distance) + centerOffset;
fruitWidgets.add(Positioned(
left: position.dx,
bottom: position.dy,
child: Container(
color: Colors.orangeAccent.withOpacity(0.8),
width: childSize,
height: childSize,
child: Center(
child: Text(
fruits[i],
style: const TextStyle(fontWeight: FontWeight.bold),
)),
),
));
}
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Container(
height: stackSize,
width: stackSize,
decoration: BoxDecoration(border: Border.all(color: Colors.black)),
child: Stack(
children: fruitWidgets,
),
),
));
}
//๊ฐ๋์ ๊ฑฐ๋ฆฌ์ ๋ฐ๋ฅธ x,y ์ขํ๊ฐ ๊ตฌํ๊ธฐ
Offset getPosition({required double degree, required double distance}) {
final double radian = getRadian(degree); //๊ฐ๋๋ฅผ ๋ผ๋์ ๋จ์๋ก ๋ณ๊ฒฝ
final double dx = distance * cos(radian);
final double dy = distance * sin(radian);
return Offset(dx, dy);
}
//๋ผ๋์๊ฐ์ผ๋ก ๋ณํํ๋ ๋ฉ์๋
double getRadian(double degree) {
return (pi / 180) * degree;
}
}
1. ์คํ์ ์ค์์์น๋ฅผ ๊ตฌํ๋๋ก ํ๋ค. ์ฌ๊ธฐ์ ๋๋ ์คํ์ ์ ์ฒดํฌ๊ธฐ๋ฅผ ์๊ณ ์๊ธฐ ๋๋ฌธ์ ์ค์์ ์ขํ๋ ํฌ๊ธฐ/2๊ฐ ๋๋ค.
ํ์ง๋ง ์์ ์์ ฏ ์ข์ธก ์๋จ์ด ๊ธฐ์ค์ ์ด ๋๋ฏ๋ก ์์์์ ฏ์ ํฌ๊ธฐ/2๋ฅผ ๋ ๋นผ์ฃผ๋ฉด ์์์์ ฏ์ด ๋ฐฐ์น ๋์ ๋ ์ ํํ ์ค์ ์์น๋ก ๋ฐฐ์น๋๋ ๊ฒ์ฒ๋ผ ๋ณด์ด๊ฒ ํ ์ ์๋ค.
2. ์์ ๊ฐ๋์ธ 360๋๋ฅผ ๊ณผ์ผ์ ๊ฐ์๋งํผ ๋๋ ์ ๊ณผ์ผ ๊ฐ์ ๊ฐ๊ฒฉ์ ์ผ์ ํ๊ฒ ๋๊ธฐ ์ํ ๊ฐ๋๋ฅผ ๊ตฌํ๋ค.
3. ๊ณผ์ผ์ ๊ฐ์๋งํผ ๋ฐ๋ณตํ์ฌ ์ฌํ๊ป ๋ณต์ตํ ์ํ๊ณต์์ ์ ์ฉํด ์ขํ๋ฅผ ์ ์ฉํ๋ค.
๋ง์ฝ ์ฌ๊ธฐ์ stack์ ์ฌ์ด์ฆ๊ฐ ๋ฐ๋๊ฒ ๋๋ค๋ฉด distance์ ๊ฐ๋ ๋ฐ๊ฟ์ค์ผ ํ๋๋ฐ distance์ ๊ฐ์ ์์ stackSize/1.5๋ก ๋๋ฉด stackSize๊ฐ ๋ฐ๋์ด๋ ์์ ํฌ๊ธฐ๋ ๋ฐฐ์น๋ ์ ์งํ ์ ์๋ค. (์์ ์์ ฏ์ ํฌ๊ธฐ๋ ๋๋ ์ผ stackSize ๊ฐ ์ค์ด๋ค์ด๋ ์์ ํฌ๊ธฐ๊ฐ ์ค์ด๋ค ๊ฑฐ๋ค)
๋ด ํฌํธํด๋ฆฌ์ค ์น์์๋ ์ด๋ฅผ ์ ์ฉํด์ ์ํ ๋ ์ด์์์ ์์ฑํ ๋ชจ์ต์ด๋ค.
๋์ ๊ฒฝ์ฐ์ MediaQuery.sizeOf(context)๋ฅผ ํตํด ์ฐฝ์ ์ฌ์ด์ฆ์ ๋ฐ๋ผ stackSize, iconsSize๋ค์ด ๋ณํ๋๋ก ๋ง๋ค์๋ค.