개요

UnconstrainedBox는 자식에게 어떠한 제약 조건도 부과하지 않아, 자식이 자신의 ‘자연스러운’ 크기로 렌더링되도록 하는 위젯입니다.

이를 통해 자식은 마치 무한한 캔버스에 제약 없이 혼자 있는 것처럼 원하는 크기로 렌더링할 수 있습니다. UnconstrainedBox는 자신의 제약 조건 내에서 자식과 동일한 크기를 채택하려고 시도합니다. 다른 크기가 되는 경우, alignment 속성에 따라 자식을 정렬합니다. 상자가 자식 전체를 수용할 만큼 확장할 수 없는 경우, 자식은 클리핑됩니다.

참조: https://api.flutter.dev/flutter/widgets/UnconstrainedBox-class.html

언제 사용하나요?

  • 자식이 부모의 제약 조건에 영향받지 않고 자체 크기로 렌더링되어야 할 때
  • 특정 축(가로 또는 세로)의 제약만 해제하고 싶을 때
  • 오버플로우 디버깅을 위해 제약 조건을 일시적으로 제거할 때
  • 자식 위젯의 실제 크기를 파악하고 싶을 때
  • 부모의 tight 제약 조건을 무시하고 자식이 유연한 크기를 가지게 하고 싶을 때

기본 사용법

// 모든 제약 조건 제거
UnconstrainedBox({
  child: Container({
    width: 200,
    height: 100,
    color: 'blue'
  })
})

// 세로 축만 제약 해제 (가로는 부모 따름)
UnconstrainedBox({
  constrainedAxis: 'horizontal',
  child: Container({
    width: 200,  // 부모 너비를 따름
    height: 100, // 자유롭게 100으로 설정
    color: 'green'
  })
})

// 정렬과 함께 사용
UnconstrainedBox({
  alignment: Alignment.topLeft,
  child: Container({
    width: 50,
    height: 50,
    color: 'red'
  })
})

Props 상세 설명

constrainedAxis

값: ‘vertical’ | ‘horizontal’ | undefined

특정 축의 제약 조건만 유지하고 싶을 때 사용합니다.

  • undefined (기본값): 모든 축의 제약 조건 제거
  • 'vertical': 세로 축의 제약은 유지, 가로 축만 제약 해제
  • 'horizontal': 가로 축의 제약은 유지, 세로 축만 제약 해제
// 세로 축 제약 유지 (가로만 자유)
UnconstrainedBox({
  constrainedAxis: 'vertical',
  child: Container({
    width: 300,   // 자유롭게 설정 가능
    height: 100,  // 부모의 세로 제약을 따름
    color: 'blue'
  })
})

// 가로 축 제약 유지 (세로만 자유)
UnconstrainedBox({
  constrainedAxis: 'horizontal',
  child: Container({
    width: 300,   // 부모의 가로 제약을 따름
    height: 100,  // 자유롭게 설정 가능
    color: 'green'
  })
})

alignment

값: Alignment (기본값: Alignment.center)

자식이 UnconstrainedBox보다 작을 때 정렬 방식을 지정합니다.

UnconstrainedBox({
  alignment: Alignment.topRight,
  child: Container({
    width: 50,
    height: 50,
    color: 'purple'
  })
})

UnconstrainedBox({
  alignment: Alignment.bottomCenter,
  child: Text('Bottom Aligned Text')
})

clipped

값: boolean (기본값: false)

자식이 UnconstrainedBox의 경계를 넘어갈 때 클리핑 여부를 결정합니다.

  • false: 오버플로우 허용 (디버깅 시 유용)
  • true: 경계를 넘어가는 부분 클리핑
// 오버플로우 허용
UnconstrainedBox({
  clipped: false,
  child: Container({
    width: 500,
    height: 500,
    color: 'red'
  })
})

// 오버플로우 클리핑
UnconstrainedBox({
  clipped: true,
  child: Container({
    width: 500,
    height: 500,
    color: 'blue'
  })
})

child

값: Widget | undefined

제약 조건이 제거될 자식 위젯입니다.

실제 사용 예제

예제 1: 반응형 버튼 그룹

const ResponsiveButtonGroup = ({ buttons }) => {
  return Row({
    mainAxisAlignment: MainAxisAlignment.center,
    children: buttons.map(button => 
      Padding({
        padding: EdgeInsets.symmetric({ horizontal: 8 }),
        child: UnconstrainedBox({
          child: ElevatedButton({
            onPressed: button.onPressed,
            child: Padding({
              padding: EdgeInsets.symmetric({ 
                horizontal: 24, 
                vertical: 12 
              }),
              child: Text(button.label, {
                style: TextStyle({
                  fontSize: 16,
                  fontWeight: 'bold'
                })
              })
            })
          })
        })
      })
    )
  });
};

예제 2: 오버플로우 감지 디버그 도구

const DebugOverflowWrapper = ({ child, showBounds }) => {
  return Stack({
    children: [
      UnconstrainedBox({
        clipped: false,
        child: child
      }),
      if (showBounds) 
        Positioned.fill({
          child: Container({
            decoration: BoxDecoration({
              border: Border.all({
                color: 'red',
                width: 2,
                style: 'dashed'
              })
            })
          })
        })
    ]
  });
};

// 사용 예
DebugOverflowWrapper({
  showBounds: true,
  child: Container({
    width: 400,
    height: 100,
    color: 'rgba(0, 0, 255, 0.3)',
    child: Center({
      child: Text('이 컨테이너는 부모보다 클 수 있습니다')
    })
  })
});

예제 3: 동적 크기 카드

const DynamicSizeCard = ({ title, content, maxWidth }) => {
  return Center({
    child: UnconstrainedBox({
      child: Container({
        constraints: BoxConstraints({
          maxWidth: maxWidth || 600,
          minWidth: 200
        }),
        padding: EdgeInsets.all(24),
        decoration: BoxDecoration({
          color: 'white',
          borderRadius: BorderRadius.circular(12),
          boxShadow: [
            BoxShadow({
              color: 'rgba(0,0,0,0.1)',
              blurRadius: 10,
              offset: { x: 0, y: 4 }
            })
          ]
        }),
        child: Column({
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(title, {
              style: TextStyle({
                fontSize: 24,
                fontWeight: 'bold'
              })
            }),
            SizedBox({ height: 16 }),
            Text(content, {
              style: TextStyle({
                fontSize: 16,
                color: '#666'
              })
            }),
            SizedBox({ height: 24 }),
            UnconstrainedBox({
              alignment: Alignment.centerRight,
              child: ElevatedButton({
                onPressed: () => console.log('Clicked'),
                child: Text('자세히 보기')
              })
            })
          ]
        })
      })
    })
  });
};

예제 4: 축별 제약 해제 레이아웃

const FlexibleLayout = ({ orientation, children }) => {
  return Container({
    color: '#f5f5f5',
    padding: EdgeInsets.all(16),
    child: Column({
      children: [
        // 헤더는 전체 너비 사용
        Container({
          height: 60,
          color: '#2196F3',
          child: Center({
            child: Text('Header', {
              style: TextStyle({
                color: 'white',
                fontSize: 20
              })
            })
          })
        }),
        SizedBox({ height: 16 }),
        // 콘텐츠는 가로 제약 해제
        UnconstrainedBox({
          constrainedAxis: 'vertical',
          alignment: Alignment.topCenter,
          child: Container({
            width: 800, // 부모보다 넓을 수 있음
            padding: EdgeInsets.all(20),
            decoration: BoxDecoration({
              color: 'white',
              borderRadius: BorderRadius.circular(8)
            }),
            child: Column({
              children: children
            })
          })
        })
      ]
    })
  });
};

예제 5: 툴팁 포지셔닝

const TooltipWithUnconstrainedBox = ({ target, tooltip, isVisible }) => {
  return Stack({
    clipBehavior: 'none',
    children: [
      target,
      if (isVisible)
        Positioned({
          top: -40,
          left: 0,
          right: 0,
          child: UnconstrainedBox({
            child: Container({
              padding: EdgeInsets.symmetric({ 
                horizontal: 16, 
                vertical: 8 
              }),
              decoration: BoxDecoration({
                color: 'rgba(0,0,0,0.8)',
                borderRadius: BorderRadius.circular(4)
              }),
              child: Text(tooltip, {
                style: TextStyle({
                  color: 'white',
                  fontSize: 14
                })
              })
            })
          })
        })
    ]
  });
};

// 사용 예
TooltipWithUnconstrainedBox({
  isVisible: showTooltip,
  tooltip: '이것은 제약 없는 툴팁입니다. 내용에 따라 크기가 자동 조정됩니다.',
  target: IconButton({
    icon: Icons.info,
    onPressed: () => setShowTooltip(!showTooltip)
  })
});

주의사항

  • UnconstrainedBox를 과도하게 사용하면 레이아웃 오버플로우가 발생할 수 있습니다
  • 프로덕션 환경에서는 clipped: true를 사용하여 오버플로우를 방지하는 것이 좋습니다
  • 디버깅 목적으로 사용할 때는 개발 환경에서만 사용하세요
  • 중첩된 UnconstrainedBox는 예상치 못한 레이아웃 문제를 일으킬 수 있습니다
  • 성능에 민감한 부분에서는 신중하게 사용하세요

관련 위젯

  • ConstrainedBox: 자식에게 추가 제약 조건을 부과
  • LimitedBox: 제약이 없을 때만 크기 제한을 적용
  • OverflowBox: 부모의 제약 조건을 무시하고 다른 제약 조건 부과
  • SizedBox: 고정된 크기를 지정
  • FittedBox: 자식을 부모에 맞게 스케일링