๐ŸŒ web

[Flutter] ๊พน ๋ˆ„๋ฅด๊ธฐ ํšจ๊ณผ ์ฃผ๊ธฐ

c0zi 2024. 10. 1. 17:23

ํ”„๋กœ์ ํŠธ ๋ง‰๋ฐ”์ง€๋กœ ์ ‘์–ด๋“ค๋ฉด์„œ, ํ•„์ˆ˜ ๊ธฐ๋Šฅ ๊ตฌํ˜„์€ ๋๋‚˜๊ฐ€๊ณ  ๋‹ค๋ฅธ ๊ฒƒ๋“ค์— ์š•์‹ฌ์ด ์ƒ๊ธฐ๊ธฐ ์‹œ์ž‘ํ–ˆ๋‹ค.

 

์˜ค๋Š˜์˜ ํฌ์ŠคํŒ…์€ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„ ์„ ์œ„ํ•œ ๊ณ ๋ฏผ์—์„œ ์‹œ์ž‘๋˜์—ˆ๋‹ค.

 

์ด๋ฒˆ์— ์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ์˜ ์ฃผ์ œ๋Š” '์‚ฌ์šฉ์ž ๋งž์ถค ๋Ÿฌ๋‹ ์ฝ”์Šค ์ถ”์ฒœ'์ด๋‹ค.

 

๊ฒฝ์‚ฌ๋„ / ์‚ฌ์šฉ์ž์˜ ์ทจํ–ฅ ๋“ฑ์„ ๋ถ„์„ํ•ด ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ฐ€์žฅ ์ž˜ ๋งž๋Š” ๋Ÿฌ๋‹ ์ฝ”์Šค๋“ค์„ ์ถ”์ฒœํ•ด ์ฃผ๋Š” ์„œ๋น„์Šค๋ฅผ ๊ฐœ๋ฐœํ•˜๊ณ  ์žˆ๋‹ค.

 


๐Ÿšจ ์ˆ˜์ •ํ•  ๋ถ€๋ถ„

๋ฉ”์ธ ํŽ˜์ด์ง€

 

์šฐ๋ฆฌ ์„œ๋น„์Šค์˜ ๋ฉ”์ธ ํ™”๋ฉด์ด๋‹ค. ์—„์ฒญ ์—ด์‹ฌํžˆ ๋งŒ๋“ค์—ˆ๋Š”๋ฐ ๋งŒ๋“ค๊ณ  ๋ณด๋‹ˆ ์กฐ๊ธˆ ๋ถ€์กฑํ•ด๋ณด์—ฌ์„œ ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์„ ๋„ฃ์–ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค.

 

๊ธฐ์กด์˜ ์„œ๋น„์Šค๋Š” ์ด๋ฏธ์ง€ ๋‚ด์˜ ์ฝ”์Šค๋ฅผ ํด๋ฆญํ•˜๋ฉด ์ƒ์„ธ ์กฐํšŒ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ด ์ฝ”์Šค๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐฉ์‹์ด๋‹ค.

 

์ฝ”์Šค ์ถ”์ฒœ์ด๋ผ๋Š” ์„œ๋น„์Šค ํŠน์„ฑ ์ƒ ์ฝ”์Šค๊ฐ€ ๊ฝค ์ค‘์š”ํ•œ๋ฐ, ์ƒ์„ธ ์กฐํšŒ๋กœ ๋“ค์–ด๊ฐ€์„œ ๋งค๋ฒˆ ์ฝ”์Šค๋ฅผ ํ™•์ธํ•˜๊ณ  ๋‹ค์‹œ ๋‚˜์™€์„œ ๋‹ค๋ฅธ ์ฝ”์Šค๋ฅผ ํ™•์ธํ•ด์•ผ ํ•œ๋‹ค๋Š” ๊ฒƒ์€ ๊ฝค ๋ถˆํŽธํ•  ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

 

๊ทธ๋ž˜์„œ, ์„œ๋น„์Šค ์‚ฌ์šฉ์ž์˜ ์ฝ”์Šค ํ™•์ธ์„ ๋” ๊ฐ„ํŽธํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๊ธฐ ์œ„ํ•œ ๋ฐฉ๋ฒ•์„ ์ฐพ๋‹ค๊ฐ€ long press ํšจ๊ณผ๋ฅผ ํ†ตํ•ด ๋Œ€๋žต์ ์ธ ์ฝ”์Šค ์ •๋ณด๋ฅผ ํ™•์ธํ•˜๊ฒŒ ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

 

๐Ÿ’จ ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ ํšจ๊ณผ๋ฅผ ํ†ตํ•ด  overlay ๋„์šฐ๊ธฐ

 

์ด๋ฅผ ์œ„ํ•ด ๊ตฌํ˜„ํ•œ ๋‚ด์šฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค

 

import 'package:flutter/material.dart';
import 'package:frontend/models/course.dart';
import 'package:frontend/widgets/course/level_badge.dart';
import 'package:get/get.dart';

class CourseCard extends StatelessWidget {
  final Course course;

  CourseCard({required this.course});

  OverlayEntry? _overlayEntry;
  void _showOverlay(BuildContext context, Offset position) {
    _overlayEntry = OverlayEntry(
      builder: (context) => Positioned(
        // position ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด ํŒ์—… ์œ„์น˜ ์„ค์ •
        top: position.dy,
        left: position.dx,
        child: Material(
          color: Colors.transparent,
          child: Container(
            padding: EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(12),
              boxShadow: [
                BoxShadow(
                  color: Colors.black26,
                  blurRadius: 10,
                  offset: Offset(0, 4),
                ),
              ],
            ),
            width: 200,
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Text('Course Name: ${course.name}'),
                Text('Length: ${(course.courseLength * 10).round() / 10} km'),
                Text('Level: ${course.level}'),
                if (course.courseType == 'user')
                  Text('By: ${course.memberNickname}'),
                Text('Participants: ${course.count}๋ช… ์ฐธ์—ฌ ์ค‘'),
              ],
            ),
          ),
        ),
      ),
    );

    Overlay.of(context)?.insert(_overlayEntry!);
  }

  void _removeOverlay() {
    _overlayEntry?.remove();
    _overlayEntry = null;
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onLongPressStart: (LongPressStartDetails details) {
        // ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ ์‹œ์ž‘ํ•  ๋•Œ ํด๋ฆญ ์œ„์น˜์— ์˜ค๋ฒ„๋ ˆ์ด๋กœ ํŒ์—… ๋„์šฐ๊ธฐ
        _showOverlay(context, details.globalPosition);
      },
      onLongPressEnd: (_) {
        // ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ๋ฅผ ๋๋‚ผ ๋•Œ ํŒ์—… ๋‹ซ๊ธฐ
        _removeOverlay();
      },
      child: Card(// detail)
    );
  }
}

 

overlay ํšจ๊ณผ


๋Œ€๋žต์ ์œผ๋กœ ์ฝ”์Šค ์ด๋ฆ„, ๊ธธ์ด, ๋‚œ์ด๋„, ์ฐธ์—ฌ ์ค‘์ธ ์‚ฌ๋žŒ ์ˆ˜๋ฅผ ํฌํ•จํ•ด๋ณด์•˜๋‹ค.

 

ํด๋ฆญํ•œ ์œ„์น˜์—์„œ overlay๊ฐ€ ๋œจ๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด onLongPressStart์—์„œ LongPressStartDetails๋ฅผ ๋ฐ›์•˜๋‹ค

 

<์˜ˆ์‹œ์ฝ”๋“œ>

onLongPressStart: (LongPressStartDetails details) {
    // ๊ธธ๊ฒŒ ๋ˆ„๋ฅด๊ธฐ ์‹œ์ž‘ํ•  ๋•Œ ํด๋ฆญ ์œ„์น˜์— ์˜ค๋ฒ„๋ ˆ์ด๋กœ ํŒ์—… ๋„์šฐ๊ธฐ
    _showOverlay(context, details.globalPosition);
},

 

์ด์ œ overlay๋ฅผ ๊พธ๋ฏธ๊ณ , ์ฝ”์Šค ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค.

 

์ฝ”์Šค ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์€ ๋‹ค๋ฅธ ํŒŒํŠธ์˜ ์ฝ”์Šค ๋“ฑ๋ก์ด ์™„๋ฃŒ๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ง€๊ธˆ์€ ์ž„์‹œ๋กœ ๊ฒ€์€์ƒ‰ ํ™”๋ฉด์œผ๋กœ ์ฒ˜๋ฆฌํ•˜์˜€๋‹ค.

 

๋ฌธ์ œ ๋ฐœ์ƒ

 

ํ•„์š”ํ•œ ์ •๋ณด์ธ ์ œ๋ชฉ๊ณผ ๋งต ๋ถ€๋ถ„์„ ์ž„์‹œ๋กœ Container ์ฒ˜๋ฆฌ ํ•ด์„œ ๋„์šฐ๊ธฐ๊นŒ์ง€ ํ–ˆ๋Š”๋ฐ ํด๋ฆญ ์œ„์น˜๋กœ ๋งต์„ ๋„์šฐ๋‹ค ๋ณด๋‹ˆ ํด๋ฆญ ํ•˜๋Š” ์œ„์น˜์— ๋”ฐ๋ผ ๋งต์ด ์งค๋ฆฌ๊ฒŒ ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ๋‹ค.

 

๊ฒฐ๋ก ์ ์œผ๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด ์กฐ๊ฑด๋ฌธ์„ ์„ค์ •ํ•ด์ฃผ์–ด pop up์ด ๋ฐ”๊นฅ์œผ๋กœ ๋‚˜๊ฐ€์ง€ ์•Š๋„๋ก ํ–ˆ๋‹ค

 

// ํŒ์—… ํฌ๊ธฐ ์„ค์ •
double popupWidth = screenWidth * 1 / 2;
double popupHeight = 210; // ์˜ˆ์‹œ๋กœ ์„ค์ •ํ•œ ๋†’์ด

// ํ™”๋ฉด ๊ฒฝ๊ณ„๋ฅผ ๋„˜์ง€ ์•Š๋„๋ก ์กฐ์ •
double leftPosition = position.dx;
double topPosition = position.dy;

// ์˜ค๋ฅธ์ชฝ์œผ๋กœ ๋„˜์ง€ ์•Š๋„๋ก ์ฒ˜๋ฆฌ
if (leftPosition + popupWidth > screenWidth) {
  leftPosition = screenWidth - popupWidth - 20; // ์˜ค๋ฅธ์ชฝ ๊ฒฝ๊ณ„์—์„œ 20px ์—ฌ์œ ๋ฅผ ๋‘ 
}

// ์•„๋ž˜์ชฝ์œผ๋กœ ๋„˜์ง€ ์•Š๋„๋ก ์ฒ˜๋ฆฌ
if (topPosition + popupHeight > screenHeight) {
  topPosition = screenHeight - popupHeight - 40; // ์•„๋ž˜์ชฝ ๊ฒฝ๊ณ„์—์„œ 40px ์—ฌ์œ ๋ฅผ ๋‘ 
}

 

์ˆ˜์ • ์™„๋ฃŒ โญ

 

์ด๋ ‡๊ฒŒ ์ตœ์ข… ์ˆ˜์ •ํ–ˆ๋‹ค !!