본문 바로가기

운동하는 개발자/Flutter

13년차 응용프로그래머, Gemini에게 Flutter 배우기 - 2

728x90

다음으로 레이아웃 매핑에 대해서 학습시킴 (Gemini가 나를)

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('레이아웃 테스트')),
    body: Padding(
      // 1. 전체 화면 테두리에 16만큼 여백을 줍니다. (Delphi의 BorderWidth 역할)
      padding: const EdgeInsets.all(16.0),
      
      // 2. 가로로 위젯들을 배치합니다.
      child: Row(
        // 양쪽 끝으로 위젯들을 밀어냅니다.
        mainAxisAlignment: MainAxisAlignment.spaceBetween, 
        children: [
          // 3. 좌측 아이템: 세로로 텍스트 2개를 배치합니다.
          Column(
            crossAxisAlignment: CrossAxisAlignment.start, // 왼쪽 정렬
            mainAxisSize: MainAxisSize.min, // 내용물 크기만큼만 세로 공간 차지
            children: const [
              Text('키오스크 주문 (일반)', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
              SizedBox(height: 4), // 텍스트 사이의 간격을 띄우기 위한 투명 박스
              Text('승인일시: 2026-05-22 15:19', style: TextStyle(color: Colors.grey)),
            ],
          ),
          
          // 4. 우측 아이템: 결제 금액
          const Text(
            '15,000원', 
            style: TextStyle(fontSize: 20, color: Colors.blueAccent, fontWeight: FontWeight.bold)
          ),
        ],
      ),
    ),
  );
}

build에 body내부에서 padding을 넣고 child, children으로 각 위젯들을 배치하는 방법에 대해서 학습함
SizedBox로 공간을 띄우는게 독특고 Column과 Row를 이용하여 가로, 세로 배치에 대해서 알게됨


// 가상의 결제 데이터 리스트
final List<Map<String, dynamic>> orderList = [
  {'title': '키오스크 주문 (일반)', 'date': '2026-05-22 15:19', 'amount': '15,000원'},
  {'title': '키오스크 주문 (포장)', 'date': '2026-05-22 15:30', 'amount': '8,500원'},
  {'title': '앱 주문 (배달)', 'date': '2026-05-22 16:05', 'amount': '22,000원'},
  {'title': '키오스크 주문 (일반)', 'date': '2026-05-22 16:12', 'amount': '4,500원'},
  {'title': '앱 주문 (포장)', 'date': '2026-05-22 16:40', 'amount': '12,000원'},
];
@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('주문 내역 리스트')),
    
    // 이전의 Padding 대신 ListView.builder를 사용합니다.
    body: ListView.builder(
      itemCount: orderList.length, // 1. 몇 개를 그릴 것인가?
      
      // 2. 각 행(Row)을 어떻게 그릴 것인가? (index는 0부터 시작)
      itemBuilder: (context, index) {
        final order = orderList[index]; // 현재 순서의 데이터 꺼내기
        
        return Container(
          // 항목과 항목 사이에 구분선 느낌을 주기 위해 테두리 추가
          decoration: const BoxDecoration(
            border: Border(bottom: BorderSide(color: Colors.black12)),
          ),
          padding: const EdgeInsets.all(16.0),
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  // 하드코딩된 텍스트 대신 변수(order)에서 값을 꺼내 출력합니다.
                  Text(order['title'], style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                  const SizedBox(height: 4),
                  Text('승인일시: ${order['date']}', style: const TextStyle(color: Colors.grey)),
                ],
              ),
              Text(
                order['amount'], 
                style: const TextStyle(fontSize: 20, color: Colors.blueAccent, fontWeight: FontWeight.bold)
              ),
            ],
          ),
        );
      },
    ),
  );
}

body에 ListView.builder를 만들어서 List의 데이터를 화면에 뿌리는 방법에 대해서 학습


// 상세 화면 위젯
class OrderDetailScreen extends StatelessWidget {
  // Delphi에서 생성자 파라미터로 데이터를 넘기듯, 생성자를 통해 데이터를 받습니다.
  final Map<String, dynamic> orderData;

  const OrderDetailScreen({super.key, required this.orderData});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('주문 상세 정보')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(orderData['title'], style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
            const SizedBox(height: 10),
            Text('결제 금액: ${orderData['amount']}', style: const TextStyle(fontSize: 20, color: Colors.blue)),
            Text('승인 시간: ${orderData['date']}', style: const TextStyle(color: Colors.grey)),
            const SizedBox(height: 30),
            
            // MFC의 EndDialog()나 Delphi의 Close와 같은 역할
            ElevatedButton(
              onPressed: () => Navigator.pop(context), 
              child: const Text('목록으로 돌아가기'),
            ),
          ],
        ),
      ),
    );
  }
}
// ListView.builder의 itemBuilder 내부 리턴문 수정
itemBuilder: (context, index) {
  final order = orderList[index];
  
  return InkWell(
    // 1. 클릭 이벤트 리스너 등록
    onTap: () {
      // 2. Navigator를 통해 상세 화면을 스택에 push
      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => OrderDetailScreen(orderData: order), // 데이터 전달
        ),
      );
    },
    child: Container(
      decoration: const BoxDecoration(
        border: Border(bottom: BorderSide(color: Colors.black12)),
      ),
      padding: const EdgeInsets.all(16.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(order['title'], style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
              const SizedBox(height: 4),
              Text('승인일시: ${order['date']}', style: const TextStyle(color: Colors.grey)),
            ],
          ),
          Text(order['amount'], style: const TextStyle(fontSize: 20, color: Colors.blueAccent, fontWeight: FontWeight.bold)),
        ],
      ),
    ),
  );
},

Navigation을 사용해서 화면을 쌓는 동작에 대해 학습(push, pop), InkWell를 사용해서 클릭리스너를 등록가능 OnTap()으로 동작을 만듦


추가로 Fullter는 Debugging때 Dart VM을 띄워서 핫 디로딩을 사용
그래서 코드를 수정하면 스크립트 언어처럼 즉시 반영됨
하지만 Release 때는 네이티브 기계어로 컴파일해서 동작시 속도가 빠름

728x90