fix(splash): 배경 아이콘 겹침 최소화

This commit is contained in:
JiWoong Sul
2025-12-03 18:55:49 +09:00
parent 095222ef61
commit 04b1c3e987

View File

@@ -19,6 +19,8 @@ class _SplashScreenState extends State<SplashScreen>
late List<AnimationController> _foodControllers; late List<AnimationController> _foodControllers;
late AnimationController _questionMarkController; late AnimationController _questionMarkController;
late AnimationController _centerIconController; late AnimationController _centerIconController;
List<Offset>? _iconPositions;
Size? _lastScreenSize;
final List<IconData> foodIcons = [ final List<IconData> foodIcons = [
Icons.rice_bowl, Icons.rice_bowl,
@@ -62,6 +64,64 @@ class _SplashScreenState extends State<SplashScreen>
)..repeat(reverse: true); )..repeat(reverse: true);
} }
List<Offset> _generateIconPositions(Size screenSize) {
final random = math.Random();
const iconSize = 40.0;
const maxScale = 1.5;
const margin = iconSize * maxScale;
const overlapThreshold = 0.3;
final effectiveSize = iconSize * maxScale;
final center = Offset(screenSize.width / 2, screenSize.height / 2);
final centerSafeRadius =
math.min(screenSize.width, screenSize.height) * 0.18;
Offset randomPosition() {
final x =
margin +
random.nextDouble() * (screenSize.width - margin * 2).clamp(1, 9999);
final y =
margin +
random.nextDouble() * (screenSize.height - margin * 2).clamp(1, 9999);
return Offset(x, y);
}
final positions = <Offset>[];
var attempts = 0;
const maxAttempts = 500;
while (positions.length < foodIcons.length && attempts < maxAttempts) {
attempts++;
final candidate = randomPosition();
if ((candidate - center).distance < centerSafeRadius) {
continue;
}
final hasHeavyOverlap = positions.any(
(p) => _isOverlapTooHigh(p, candidate, effectiveSize, overlapThreshold),
);
if (hasHeavyOverlap) {
continue;
}
positions.add(candidate);
}
while (positions.length < foodIcons.length) {
positions.add(randomPosition());
}
return positions;
}
bool _isOverlapTooHigh(Offset a, Offset b, double size, double maxRatio) {
final dx = (a.dx - b.dx).abs();
final dy = (a.dy - b.dy).abs();
final overlapX = math.max(0.0, size - dx);
final overlapY = math.max(0.0, size - dy);
final overlapArea = overlapX * overlapY;
final maxArea = size * size;
if (maxArea == 0) return false;
return overlapArea / maxArea > maxRatio;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDark = Theme.of(context).brightness == Brightness.dark; final isDark = Theme.of(context).brightness == Brightness.dark;
@@ -145,15 +205,22 @@ class _SplashScreenState extends State<SplashScreen>
} }
List<Widget> _buildFoodIcons() { List<Widget> _buildFoodIcons() {
final random = math.Random(); final screenSize = MediaQuery.of(context).size;
final sameSize =
_lastScreenSize != null &&
(_lastScreenSize!.width - screenSize.width).abs() < 1 &&
(_lastScreenSize!.height - screenSize.height).abs() < 1;
if (_iconPositions == null || !sameSize) {
_iconPositions = _generateIconPositions(screenSize);
_lastScreenSize = screenSize;
}
return List.generate(foodIcons.length, (index) { return List.generate(foodIcons.length, (index) {
final left = random.nextDouble() * 0.8 + 0.1; final position = _iconPositions![index];
final top = random.nextDouble() * 0.7 + 0.1;
return Positioned( return Positioned(
left: MediaQuery.of(context).size.width * left, left: position.dx,
top: MediaQuery.of(context).size.height * top, top: position.dy,
child: FadeTransition( child: FadeTransition(
opacity: Tween(begin: 0.2, end: 0.8).animate( opacity: Tween(begin: 0.2, end: 0.8).animate(
CurvedAnimation( CurvedAnimation(