개요

Expanded는 Row, Column, Flex 내에서 자식 위젯이 주축(main axis) 방향의 사용 가능한 공간을 모두 차지하도록 확장하는 위젯입니다.

Expanded는 Flexible 위젯의 특별한 경우로, fit: FlexFit.tight 속성이 적용된 상태입니다. 즉, 자식 위젯이 할당된 공간을 반드시 모두 채우도록 강제합니다. 여러 Expanded 위젯이 있을 때는 flex 값에 따라 공간을 비율적으로 분배합니다.

참고: https://api.flutter.dev/flutter/widgets/Expanded-class.html

언제 사용하나요?

  • Row/Column 내에서 남은 공간을 모두 차지하는 위젯이 필요할 때
  • 여러 위젯 간에 공간을 비율적으로 분배할 때
  • TextField나 다른 입력 위젯이 가능한 모든 가로 공간을 사용하게 할 때
  • 반응형 레이아웃을 만들 때
  • Spacer를 사용하여 위젯들을 밀어낼 때

기본 사용법

// Row에서 남은 공간 채우기
Row({
  children: [
    Container({ width: 100, height: 50, color: 'blue' }),
    Expanded({
      child: Container({ height: 50, color: 'red' })
    }),
    Container({ width: 100, height: 50, color: 'green' })
  ]
})

// Column에서 사용
Column({
  children: [
    Container({ height: 100, color: 'blue' }),
    Expanded({
      child: Container({ color: 'red' }) // 남은 세로 공간 모두 차지
    }),
    Container({ height: 100, color: 'green' })
  ]
})

Props

child (필수)

값: Widget

Expanded로 확장할 자식 위젯입니다.

Expanded({
  child: Container({ color: 'blue' })
})

flex

값: number (기본값: 1)

다른 Expanded/Flexible 위젯과 비교하여 이 위젯이 차지할 공간의 비율을 정의합니다. 값이 클수록 더 많은 공간을 차지합니다.

  • 0 이상의 정수여야 합니다 (음수 불가)
  • 기본값은 1입니다
  • 다른 flex 자식들과의 비율로 공간이 분배됩니다
Row({
  children: [
    Expanded({
      flex: 2, // 전체의 2/3 차지
      child: Container({ color: 'red' })
    }),
    Expanded({
      flex: 1, // 전체의 1/3 차지
      child: Container({ color: 'blue' })
    })
  ]
})

고급 사용법

비율적 공간 분배

Row({
  children: [
    Expanded({
      flex: 3, // 3/6 = 50%
      child: Container({ height: 100, color: 'red' })
    }),
    Expanded({
      flex: 2, // 2/6 = 33.33%
      child: Container({ height: 100, color: 'green' })
    }),
    Expanded({
      flex: 1, // 1/6 = 16.67%
      child: Container({ height: 100, color: 'blue' })
    })
  ]
})

고정 크기 위젯과 함께 사용

Row({
  children: [
    Icon({ icon: Icons.menu }), // 고정 크기
    SizedBox({ width: 16 }), // 고정 간격
    Expanded({
      child: Text('제목이 길어도 가능한 공간 내에서 표시됩니다')
    }),
    IconButton({ // 고정 크기
      icon: Icons.more_vert,
      onPressed: () => {}
    })
  ]
})

실제 사용 예제

예제 1: 검색 바

const searchBar = Container({
  padding: EdgeInsets.all(16),
  child: Row({
    children: [
      Expanded({
        child: TextField({
          decoration: InputDecoration({
            hintText: '검색어를 입력하세요',
            prefixIcon: Icon({ icon: Icons.search }),
            border: OutlineInputBorder({
              borderRadius: BorderRadius.circular(30)
            })
          })
        })
      }),
      SizedBox({ width: 12 }),
      IconButton({
        icon: Icons.filter_list,
        onPressed: () => console.log('필터 열기')
      })
    ]
  })
});

예제 2: 채팅 입력창

const chatInput = Container({
  padding: EdgeInsets.all(8),
  decoration: BoxDecoration({
    color: 'white',
    boxShadow: [
      BoxShadow({
        color: 'rgba(0, 0, 0, 0.1)',
        blurRadius: 4,
        offset: { x: 0, y: -2 }
      })
    ]
  }),
  child: Row({
    children: [
      IconButton({
        icon: Icons.attach_file,
        onPressed: () => {}
      }),
      Expanded({
        child: TextField({
          decoration: InputDecoration({
            hintText: '메시지 입력...',
            border: InputBorder.none
          }),
          maxLines: 3
        })
      }),
      IconButton({
        icon: Icons.send,
        onPressed: () => {}
      })
    ]
  })
});

예제 3: 반응형 버튼 그룹

const responsiveButtons = Container({
  padding: EdgeInsets.all(16),
  child: Row({
    children: [
      Expanded({
        child: ElevatedButton({
          onPressed: () => console.log('취소'),
          style: ButtonStyle({
            backgroundColor: 'gray'
          }),
          child: Text('취소')
        })
      }),
      SizedBox({ width: 16 }),
      Expanded({
        flex: 2, // 확인 버튼이 더 넓음
        child: ElevatedButton({
          onPressed: () => console.log('확인'),
          child: Text('확인')
        })
      })
    ]
  })
});

예제 4: Spacer 활용

// Spacer는 내부적으로 Expanded를 사용합니다
const header = Container({
  padding: EdgeInsets.all(16),
  color: 'blue',
  child: Row({
    children: [
      Text('로고', { style: TextStyle({ color: 'white', fontSize: 20 }) }),
      Spacer(), // Expanded({ child: Container() })와 동일
      IconButton({
        icon: Icons.notifications,
        color: 'white',
        onPressed: () => {}
      }),
      IconButton({
        icon: Icons.account_circle,
        color: 'white',
        onPressed: () => {}
      })
    ]
  })
});

Flexible와의 차이점

Expanded는 Flexible의 특수한 경우입니다:

// 이 두 코드는 동일합니다
Expanded({ 
  flex: 2, 
  child: widget 
})

Flexible({ 
  flex: 2, 
  fit: FlexFit.tight, // 핵심 차이점
  child: widget 
})
  • Expanded: 항상 할당된 공간을 모두 차지 (tight)
  • Flexible: 기본적으로 필요한 만큼만 차지 (loose)

주의사항

  • Expanded는 반드시 Row, Column, 또는 Flex의 직접적인 자식이어야 합니다.
  • Expanded를 다른 Expanded나 Flexible 안에 직접 중첩할 수 없습니다.
  • 부모 위젯이 주축 방향으로 무한한 크기를 가지면 오류가 발생합니다.
  • flex 값은 0 이상이어야 하며, 음수를 사용하면 오류가 발생합니다.
  • Column 안의 Column이나 Row 안의 Row에서 Expanded를 사용할 때는 내부 Column/Row를 Expanded로 감싸야 할 수 있습니다.

관련 위젯

  • Flexible: Expanded의 기반이 되는 더 유연한 위젯
  • Spacer: 빈 공간을 만들기 위한 Expanded의 특수한 사용
  • Row: 가로 방향 레이아웃에서 Expanded 사용
  • Column: 세로 방향 레이아웃에서 Expanded 사용
  • Flex: Row와 Column의 기반 위젯