$ cd /blog
오류 해결

Flutter에서 점수가 들쭉날쭉 흔들린 이유 — Flexible과 Spacer를 같이 쓰면 생기는 일

랭킹 화면의 점수가 행마다 좌우로 흔들려 원인을 추적했습니다. 범인은 Row 안에서 나란히 쓴 Flexible과 Spacer였고, Expanded 하나로 정리한 과정과 숫자 폭까지 맞추는 방법을 정리했습니다.


안녕하세요. Jay입니다!

운영 중인 앱 사용자분이 스크린샷 한 장을 보내주셨습니다. 랭킹 결과 팝업인데 점수가 오른쪽에 가지런히 안 맞고 행마다 조금씩 어긋나 보인다는 제보였어요. 코드를 열어보니 오른쪽 정렬을 하려고 Spacer()를 넣어둔 상태였는데도 그랬습니다. 원인은 예상보다 단순했고, 한 번 알고 나면 다시는 안 틀릴 종류였습니다.

🔍 증상 — 닉네임이 길수록 점수가 오른쪽으로 밀렸다

1~5위가 나란히 있는 목록에서 점수의 오른쪽 끝이 제각각이었습니다. 특히 닉네임이 긴 행(꽃을사랑하는부...)만 유독 점수가 오른쪽으로 밀려 있었죠. 반대로 닉네임이 짧은 행은 점수가 왼쪽으로 당겨져 있었습니다.

점수 위치가 닉네임 길이에 끌려다니고 있었습니다. 문제의 코드는 이랬습니다.

Row(
  children: [
    SizedBox(width: 30, child: rankBadge),
    const SizedBox(width: 8),
    Flexible(child: Text(nickname)),   // ← flex: 1
    const Spacer(),                    // ← flex: 1
    Text('$score'),
    Text('점'),
  ],
)

Spacer를 넣었으니 점수가 오른쪽 끝에 붙어야 할 것 같은데 그렇지 않았습니다.

🧩 원인 — 둘이 남는 공간을 반씩 나눠 가진다

Spacer는 사실 flex: 1짜리 빈 위젯입니다. 그리고 Flexible도 기본이 flex: 1이죠. 그래서 Row에 남은 공간이 생기면 둘이 1:1로 나눠 가집니다.

여기서 헷갈리기 쉬운 부분이 하나 더 있습니다. Flexible은 기본이 FlexFit.loose라, 자식이 필요한 만큼만 그리고 나머지는 비워둡니다. 그런데 비워둔 공간도 여전히 Flexible이 차지하고 있습니다. 반납하지 않아요.

정리하면 이렇게 됩니다.

닉네임 길이 Flexible이 받은 공간 실제로 쓴 공간 결과
짧음(칸쵸) 남은 공간의 절반 조금만 남는 자리만큼 점수가 왼쪽으로 당겨짐
김(꽃을사랑하는부…) 남은 공간의 절반 거의 다 점수가 오른쪽으로 밀림

점수는 가만히 있었는데 앞 위젯이 차지한 폭이 행마다 달라서 흔들려 보였던 겁니다.

🔧 해결 — Expanded 하나면 끝난다

Flexible + Spacer 조합을 Expanded 하나로 바꾸면 됩니다. ExpandedFlexFit.tight남는 공간을 전부 차지하고, 그 결과 뒤따르는 위젯이 자연스럽게 오른쪽 끝에 고정됩니다.

Row(
  children: [
    SizedBox(width: 30, child: rankBadge),
    const SizedBox(width: 8),
    Expanded(                          // ← Spacer 제거
      child: Text(
        nickname,
        maxLines: 1,
        overflow: TextOverflow.ellipsis,
      ),
    ),
    const SizedBox(width: 8),
    Text('$score'),
    Text('점'),
  ],
)

닉네임이 길면 ellipsis로 잘리고, 짧으면 남는 자리를 그대로 차지합니다. 어느 쪽이든 점수의 오른쪽 끝은 항상 같은 위치예요.

✨ 덤 — 숫자 폭까지 맞추기

여기까지 하면 배치는 맞는데, 자세히 보면 여전히 1~2px씩 어긋나 보일 수 있습니다. 대부분의 폰트가 비례폭(proportional) 이라 1은 좁고 8은 넓기 때문입니다. 3688132777은 같은 5자리인데도 실제 폭이 다르죠.

이럴 때 쓰는 게 고정폭 숫자(tabular figures) 입니다.

Text(
  '$score',
  style: const TextStyle(
    fontWeight: FontWeight.w900,
    fontFeatures: [FontFeature.tabularFigures()],  // ← 이 한 줄
  ),
)

FontFeaturematerial.dart만 import해도 바로 쓸 수 있습니다. 숫자마다 폭이 같아지니 세로로 딱 맞아떨어지고, 실시간으로 값이 바뀌는 카운터에서도 숫자가 덜컹거리지 않는 부수 효과까지 있습니다.

💡 Jay의 한 줄 정리

Spacer는 여백이 아니라 flex: 1 위젯입니다. 앞에 이미 Flexible이나 Expanded가 있다면 공간을 나눠 갖게 되니, "오른쪽으로 밀어붙이고 싶다"는 의도라면 Spacer를 더하는 게 아니라 앞 위젯을 Expanded로 바꾸는 게 맞습니다.

고정 여백이 필요할 뿐이라면 SizedBox(width: 8)을 쓰세요. SpacerSizedBox는 이름만 비슷하지 하는 일이 완전히 다릅니다.

결론

Flexible + Spacer를 나란히 쓰면 남는 공간을 반씩 나눠 가지면서, 앞 위젯의 내용 길이에 따라 뒤 위젯 위치가 흔들립니다. 오른쪽 정렬이 목적이라면 Expanded 하나로 충분하고, 숫자 정렬까지 완벽하게 하려면 FontFeature.tabularFigures() 를 얹으면 됩니다.

레이아웃 버그는 스크린샷으로 보면 "그냥 좀 안 맞네" 정도로 보이지만, 원인은 이렇게 위젯의 flex 규칙에 있는 경우가 많습니다. 비슷한 증상을 겪고 계시다면 Row 안에 flex를 가진 위젯이 몇 개인지부터 세어보세요.

다음에도 유익한 포스팅으로 찾아오겠습니다. 감사합니다!

#Flutter#Row#Flexible#Spacer#Expanded#레이아웃

$ ls related/

더 많은 IT·AI 활용법이 궁금하다면?

매주 새로운 실전 가이드가 업데이트됩니다.

전체 글 보기