개요

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

Positioned를 사용하면 Stack의 가장자리로부터의 거리를 지정하여 자식 위젯을 절대적으로 배치할 수 있습니다. CSS의 position: absolute와 유사한 개념으로, top, bottom, left, right 속성을 통해 위치를 제어합니다. Positioned는 반드시 Stack의 직접적인 자식이어야 합니다.

언제 사용하나요?

  • Stack 내에서 위젯을 특정 위치에 고정할 때
  • 오버레이나 플로팅 요소를 만들 때
  • 배지나 알림 표시를 다른 위젯 위에 겹칠 때
  • 커스텀 레이아웃을 구성할 때
  • FAB(Floating Action Button)과 같은 UI 요소를 배치할 때

기본 사용법

Stack({
  children: [
    // 배경 위젯
    Container({ color: 'lightgray' }),
    
    // 왼쪽 상단에 위치
    Positioned({
      top: 10,
      left: 10,
      child: Text('왼쪽 상단')
    }),
    
    // 오른쪽 하단에 위치
    Positioned({
      bottom: 10,
      right: 10,
      child: Text('오른쪽 하단')
    })
  ]
})

Props

위치 지정 Props

top

값: number | undefined

Stack의 상단 가장자리로부터의 거리를 픽셀 단위로 지정합니다.

Positioned({
  top: 20,
  child: Text('상단에서 20픽셀')
})

bottom

값: number | undefined

Stack의 하단 가장자리로부터의 거리를 픽셀 단위로 지정합니다.

Positioned({
  bottom: 20,
  child: Text('하단에서 20픽셀')
})

left

값: number | undefined

Stack의 왼쪽 가장자리로부터의 거리를 픽셀 단위로 지정합니다.

Positioned({
  left: 20,
  child: Text('왼쪽에서 20픽셀')
})

값: number | undefined

Stack의 오른쪽 가장자리로부터의 거리를 픽셀 단위로 지정합니다.

Positioned({
  right: 20,
  child: Text('오른쪽에서 20픽셀')
})

크기 지정 Props

width

값: number | undefined

자식 위젯의 너비를 명시적으로 지정합니다. left와 right를 모두 지정한 경우 width는 무시됩니다.

Positioned({
  top: 10,
  left: 10,
  width: 100,
  height: 50,
  child: Container({ color: 'blue' })
})

height

값: number | undefined

자식 위젯의 높이를 명시적으로 지정합니다. top과 bottom을 모두 지정한 경우 height는 무시됩니다.

Positioned({
  top: 10,
  left: 10,
  width: 100,
  height: 50,
  child: Container({ color: 'red' })
})

child (필수)

값: Widget

위치를 지정할 자식 위젯입니다.

Positioned({
  top: 50,
  left: 50,
  child: Icon({ icon: Icons.star, color: 'yellow' })
})

정적 메서드

Positioned.fill()

Stack 전체를 채우는 Positioned 위젯을 생성합니다. 모든 가장자리에서 0의 거리를 가집니다.

Stack({
  children: [
    Image({ src: 'background.jpg' }),
    Positioned.fill({
      child: Container({
        color: 'rgba(0, 0, 0, 0.5)' // 반투명 오버레이
      })
    })
  ]
})

// 다음과 동일합니다:
Positioned({
  top: 0,
  bottom: 0,
  left: 0,
  right: 0,
  child: Container({ color: 'rgba(0, 0, 0, 0.5)' })
})

위치와 크기 조합

대변 제약을 통한 크기 지정

left와 right를 모두 지정하면 너비가 자동으로 계산되고, top과 bottom을 모두 지정하면 높이가 자동으로 계산됩니다.

// 너비가 자동 계산됨 (Stack 너비 - 40)
Positioned({
  top: 10,
  left: 20,
  right: 20,
  child: Container({ height: 50, color: 'green' })
})

// 높이가 자동 계산됨 (Stack 높이 - 40)
Positioned({
  top: 20,
  bottom: 20,
  left: 10,
  child: Container({ width: 50, color: 'blue' })
})

실제 사용 예제

예제 1: 배지 표시

const notificationIcon = Container({
  width: 50,
  height: 50,
  child: Stack({
    children: [
      Icon({ icon: Icons.notifications, size: 40 }),
      Positioned({
        top: 0,
        right: 0,
        child: Container({
          width: 20,
          height: 20,
          decoration: BoxDecoration({
            shape: 'circle',
            color: 'red'
          }),
          alignment: Alignment.center,
          child: Text('3', {
            style: TextStyle({ color: 'white', fontSize: 12 })
          })
        })
      })
    ]
  })
});

예제 2: 이미지 위 텍스트 오버레이

const imageWithOverlay = Container({
  width: 300,
  height: 200,
  child: Stack({
    fit: StackFit.expand,
    children: [
      Image({ src: 'landscape.jpg', fit: 'cover' }),
      // 하단 그라데이션
      Positioned({
        bottom: 0,
        left: 0,
        right: 0,
        height: 80,
        child: Container({
          decoration: BoxDecoration({
            gradient: LinearGradient({
              begin: Alignment.bottomCenter,
              end: Alignment.topCenter,
              colors: ['rgba(0,0,0,0.8)', 'transparent']
            })
          })
        })
      }),
      // 텍스트
      Positioned({
        bottom: 20,
        left: 20,
        right: 20,
        child: Column({
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Beautiful Landscape', {
              style: TextStyle({
                color: 'white',
                fontSize: 24,
                fontWeight: 'bold'
              })
            }),
            Text('Photo by John Doe', {
              style: TextStyle({
                color: 'rgba(255,255,255,0.8)',
                fontSize: 14
              })
            })
          ]
        })
      })
    ]
  })
});

예제 3: 플로팅 액션 버튼

const screenWithFAB = Scaffold({
  body: Stack({
    children: [
      // 메인 컨텐츠
      ListView({
        children: List.generate(20, (i) => 
          ListTile({ title: Text(`항목 ${i + 1}`) })
        )
      }),
      // FAB
      Positioned({
        bottom: 16,
        right: 16,
        child: FloatingActionButton({
          onPressed: () => console.log('FAB 클릭'),
          child: Icon({ icon: Icons.add })
        })
      })
    ]
  })
});

예제 4: 툴팁 표시

const tooltipExample = Stack({
  clipped: false, // 툴팁이 Stack 영역을 벗어날 수 있게 함
  children: [
    ElevatedButton({
      onPressed: () => {},
      child: Text('Hover me')
    }),
    if (showTooltip) Positioned({
      bottom: 40,
      left: -20,
      child: Container({
        padding: EdgeInsets.all(8),
        decoration: BoxDecoration({
          color: 'black',
          borderRadius: BorderRadius.circular(4)
        }),
        child: Text('This is a tooltip', {
          style: TextStyle({ color: 'white' })
        })
      })
    })
  ]
});

주의사항

  • Positioned는 반드시 Stack의 직접적인 자식이어야 합니다.
  • top과 bottom을 모두 지정하면 height는 무시됩니다.
  • left와 right를 모두 지정하면 width는 무시됩니다.
  • 최소한 하나의 위치 속성(top, bottom, left, right)을 지정해야 합니다.
  • Stack의 크기가 결정되지 않은 경우 Positioned 위젯의 위치가 예상과 다를 수 있습니다.
  • Positioned 위젯은 Stack의 크기 계산에 영향을 주지 않습니다.

관련 위젯

  • Stack: Positioned가 사용되는 부모 위젯
  • Align: 단일 자식을 정렬하는 더 간단한 방법
  • Container: alignment 속성으로 간단한 정렬 가능
  • Transform: 위치 대신 변형을 통한 배치
  • CustomMultiChildLayout: 더 복잡한 레이아웃이 필요할 때