Stack으로 위젯 겹치기와 위치 지정

웹에서 절대 위치 지정이 필요할 때 CSS의 position: absolute를 사용하듯이, Flitter에서는 StackPositioned 위젯을 사용해 위젯들을 겹치고 원하는 위치에 배치할 수 있습니다.

🎯 학습 목표

이 튜토리얼을 완료하면 다음을 할 수 있게 됩니다:

  • Stack으로 위젯들을 겹쳐서 배치하기
  • Positioned로 정확한 위치 지정하기
  • Align으로 상대적 위치 지정하기
  • z-index 개념 이해하고 적용하기
  • 실용적인 오버레이 UI 만들기

📚 Stack 위젯 이해하기

Stack은 자식 위젯들을 겹쳐서 배치합니다. 나중에 추가된 위젯이 위에 그려집니다.

Stack의 기본 구조

Stack({
  alignment: Alignment.center,  // 기본 정렬
  fit: StackFit.loose,         // 크기 조정 방식
  children: [
    // 첫 번째 위젯 (맨 아래)
    // 두 번째 위젯
    // 세 번째 위젯 (맨 위)
  ]
})

StackFit 옵션

  • StackFit.loose - Stack이 자식들의 크기에 맞춤 (기본값)
  • StackFit.expand - Stack이 부모의 크기를 모두 차지
  • StackFit.passthrough - 부모의 제약을 자식에게 전달

🎨 Positioned 위젯 이해하기

Positioned는 Stack 내에서 자식 위젯의 정확한 위치를 지정합니다.

Positioned의 속성

Positioned({
  top: 10,     // 위에서부터의 거리
  right: 20,   // 오른쪽에서부터의 거리
  bottom: 30,  // 아래에서부터의 거리
  left: 40,    // 왼쪽에서부터의 거리
  width: 100,  // 너비 지정 (선택사항)
  height: 50,  // 높이 지정 (선택사항)
  child: Container({ /* ... */ })
})

🚀 실습 1: 기본 Stack 레이아웃

Stack({
  children: [
    // 배경
    Container({
      width: 300,
      height: 200,
      color: '#e3f2fd'
    }),
    
    // 중앙 요소
    Positioned({
      left: 50,
      top: 50,
      child: Container({
        width: 100,
        height: 100,
        color: '#2196f3',
        child: Center({ child: Text("중앙") })
      })
    }),
    
    // 우측 상단 요소
    Positioned({
      right: 10,
      top: 10,
      child: Container({
        width: 40,
        height: 40,
        color: '#f44336',
        child: Center({ child: Text("!") })
      })
    })
  ]
})

🚀 실습 2: 프로필 카드 만들기

Container({
  width: 300,
  height: 180,
  child: Stack({
    children: [
      // 배경 이미지 대체
      Container({
        decoration: new BoxDecoration({
          gradient: new LinearGradient({
            colors: ['#4a90e2', '#7b68ee'],
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter
          })
        })
      }),
      
      // 프로필 이미지 위치
      Positioned({
        left: 20,
        bottom: 20,
        child: Container({
          width: 80,
          height: 80,
          decoration: new BoxDecoration({
            color: 'white',
            shape: BoxShape.circle,
            border: Border.all({ color: 'white', width: 3 })
          }),
          child: Center({ child: Text("👤", { style: new TextStyle({ fontSize: 40 }) }) })
        })
      }),
      
      // 이름과 설명
      Positioned({
        left: 120,
        bottom: 50,
        child: Column({
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text("홍길동", {
              style: new TextStyle({
                color: 'white',
                fontSize: 24,
                fontWeight: 'bold'
              })
            }),
            Text("UI/UX 디자이너", {
              style: new TextStyle({
                color: 'rgba(255,255,255,0.8)',
                fontSize: 16
              })
            })
          ]
        })
      }),
      
      // 팔로우 버튼
      Positioned({
        right: 20,
        bottom: 20,
        child: Container({
          padding: EdgeInsets.symmetric({ horizontal: 20, vertical: 10 }),
          decoration: new BoxDecoration({
            color: 'white',
            borderRadius: BorderRadius.circular(20)
          }),
          child: Text("팔로우", {
            style: new TextStyle({
              color: '#4a90e2',
              fontWeight: 'bold'
            })
          })
        })
      })
    ]
  })
})

🚀 실습 3: 알림 뱃지가 있는 아이콘

Container({
  width: 60,
  height: 60,
  child: Stack({
    children: [
      // 아이콘 배경
      Container({
        width: 60,
        height: 60,
        decoration: new BoxDecoration({
          color: '#f5f5f5',
          shape: BoxShape.circle
        }),
        child: Center({
          child: Text("🔔", { style: new TextStyle({ fontSize: 30 }) })
        })
      }),
      
      // 알림 뱃지
      Positioned({
        right: 0,
        top: 0,
        child: Container({
          width: 20,
          height: 20,
          decoration: new BoxDecoration({
            color: '#f44336',
            shape: BoxShape.circle,
            border: Border.all({ color: 'white', width: 2 })
          }),
          child: Center({
            child: Text("3", {
              style: new TextStyle({
                color: 'white',
                fontSize: 12,
                fontWeight: 'bold'
              })
            })
          })
        })
      })
    ]
  })
})

💡 실용적인 예제: 이미지 갤러리 오버레이

Container({
  width: 350,
  height: 250,
  child: Stack({
    children: [
      // 배경 이미지 (색상으로 대체)
      Container({
        decoration: new BoxDecoration({
          gradient: new RadialGradient({
            colors: ['#ffd89b', '#19547b'],
            center: Alignment.center,
            radius: 1.5
          })
        })
      }),
      
      // 그라데이션 오버레이
      Container({
        decoration: new BoxDecoration({
          gradient: new LinearGradient({
            colors: ['transparent', 'rgba(0,0,0,0.7)'],
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            stops: [0.5, 1.0]
          })
        })
      }),
      
      // 제목과 설명
      Positioned({
        left: 20,
        bottom: 20,
        right: 20,
        child: Column({
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text("아름다운 일몰", {
              style: new TextStyle({
                color: 'white',
                fontSize: 24,
                fontWeight: 'bold'
              })
            }),
            SizedBox({ height: 8 }),
            Text("제주도 협재 해변에서 촬영한 환상적인 일몰 풍경", {
              style: new TextStyle({
                color: 'rgba(255,255,255,0.8)',
                fontSize: 14
              })
            })
          ]
        })
      }),
      
      // 좋아요 버튼
      Positioned({
        right: 20,
        top: 20,
        child: Container({
          width: 40,
          height: 40,
          decoration: new BoxDecoration({
            color: 'rgba(255,255,255,0.3)',
            shape: BoxShape.circle
          }),
          child: Center({
            child: Text("❤️", { style: new TextStyle({ fontSize: 20 }) })
          })
        })
      })
    ]
  })
})

🎨 연습 과제

  1. 플로팅 액션 버튼: 우측 하단에 고정된 원형 버튼 만들기
  2. 툴팁 오버레이: 호버 시 나타나는 툴팁 구현하기
  3. 이미지 텍스트 오버레이: 이미지 위에 텍스트와 그라데이션 배치하기

🚨 주의사항

  1. Positioned는 Stack 내부에서만: Stack의 직접 자식이어야 함
  2. overflow 처리: Stack 밖으로 나가는 요소 주의
  3. z-index: children 배열의 순서가 z-index 역할

🔗 다음 단계