[Android] RecyclerView 이미지 렌더링 버그들

Ju-unn·2026년 5월 22일

서버에 이미지가 잘 저장되고, 앱까지 URL도 도착했어요. 이제 화면에 그리기만 하면 끝! 이라고 생각했는데... 버그가 줄줄이 나왔어요. 이미지가 안 보이고, 보였다가도 깜빡거리고. 이 글은 제가 겪은 RecyclerView 이미지 렌더링 버그들과 해결 과정을 정리한 디버깅 종합편이에요. 실전 디버깅 흐름 그대로 담았어요.


먼저 RecyclerView가 뭔지

한 줄 요약

RecyclerView는 긴 목록을 보여줄 때, 화면에 보이는 만큼의 뷰만 만들어 재활용하는 효율적인 목록 위젯이에요.

채팅 메시지가 1000개여도, 화면에 보이는 건 10개 정도잖아요. RecyclerView는 딱 그 10개 정도의 "뷰 틀(ViewHolder)"만 만들어두고, 스크롤하면 화면 밖으로 나간 틀을 재활용해서 새 데이터를 담아요. 식당에서 접시를 무한정 만들지 않고, 다 먹은 접시를 씻어서 다시 쓰는 거랑 같아요.

이 "재활용"이 효율적이지만, 버그의 원천이기도 해요. 하나씩 볼게요.


버그 1 — 이미지가 아예 안 보임 (wrap_content + centerCrop 함정)

증상

업로드도 성공, URL도 도착, Glide(이미지 로딩 라이브러리)도 "로딩 성공!"이라고 로그를 찍었어요. 그런데 화면엔 아무것도 안 보였어요.

원인

ImageView의 크기 설정이 문제였어요.

<!-- 문제의 설정 -->
<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:scaleType="centerCrop" />

wrap_content는 "내용물 크기에 맞춰라"라는 뜻이에요. 그런데 centerCrop은 "정해진 크기를 꽉 채우도록 크롭해라"예요. 둘이 충돌해요.

크기를 내용물에 맞춰라 (wrap_content)
   ↕ 모순!
정해진 크기를 채워라 (centerCrop)
   ↓
크기가 0으로 측정됨 → 0×0 영역에 그림 → 안 보임!

이미지는 메모리에 멀쩡히 로드됐는데(그래서 Glide는 "성공"), 그릴 공간이 0×0이었던 거예요. 종이 없이 그림 그리라는 격이죠.

해결

너비를 고정하고, scaleType을 fitCenter로 바꿨어요.

<!-- 해결 -->
<ImageView
    android:layout_width="200dp"            
    android:layout_height="wrap_content"
    android:adjustViewBounds="true"          
    android:maxHeight="280dp"
    android:minHeight="120dp"
    android:scaleType="fitCenter" />         
  • layout_width="200dp": 너비를 확정 → 그릴 공간이 생김
  • adjustViewBounds="true": 높이는 이미지 비율에 맞춰 자동 조정
  • fitCenter: 이미지 전체가 잘리지 않고 비율 유지하며 들어감

💡 centerCrop vs fitCenter

scaleType동작어울리는 곳
centerCrop뷰를 꽉 채우고 넘치는 부분 자름인스타 정사각 썸네일
fitCenter뷰 안에 전체가 들어가게 (여백 가능)채팅 이미지

채팅 이미지는 사용자가 보낸 사진 전체가 보여야 하니까 fitCenter가 맞아요. centerCrop을 쓰면 세로로 긴 사진의 위아래가 잘려요.


버그 2 — 텍스트 메시지에 엉뚱한 이미지 잔상

시나리오

1번 메시지 = 이미지 → ImageView 보이게, TextView 숨김
   ↓ 스크롤로 화면 밖 → 이 ViewHolder 재활용됨
2번 메시지 = 텍스트 → TextView에 글자 세팅
   ↓ 그런데 ImageView를 다시 숨기지 않으면?
→ 2번(텍스트)인데 1번의 이미지가 그대로 남아있음!

재활용된 ViewHolder에 이전 상태가 남아있어서 생기는 버그예요.

해결 — bind는 항상 모든 상태를 명시적으로 설정

void bind(MessageData msg) {
    if (msg.isImage()) {
        textContent.setVisibility(View.GONE);     // 텍스트 숨김
        imageContent.setVisibility(View.VISIBLE);  // 이미지 보임
        Glide.with(context).load(msg.getImageUrl()).into(imageContent);
    } else {
        imageContent.setVisibility(View.GONE);     // 이미지 반드시 숨김!
        textContent.setVisibility(View.VISIBLE);   // 텍스트 보임
        textContent.setText(msg.getContent());
    }
}

핵심은 else에서 imageContent를 반드시 GONE으로 되돌리는 것이에요. "이전에 뭐였든 상관없이, 지금 이 데이터에 맞게 완전히 다시 설정"하는 거죠. 이걸 멱등성(idempotent)이라고 해요. 몇 번을 호출해도 같은 결과가 나오게요.


버그 3 — 이미지 10장 보내니 화면이 반짝반짝

증상

이미지 10장을 순서대로 올리는데, 한 장씩 도착할 때마다 화면이 깜빡거렸어요. "반짝반짝"하면서 정신없었죠.

원인 — 애니메이션 두 가지가 겹침

원인 ① Glide의 페이드인: Glide는 기본적으로 이미지가 로드되면 서서히 나타나는 효과(fade-in)를 줘요. 평소엔 예쁜데, 여러 장이 빠르게 들어오면 각각 페이드인하면서 깜빡거려요.

원인 ② 너무 빠르게 사진이 올라감 그래서 순차적으로 이미지가 올라가지만 그 시간에 350ms 단위로 올라가게 해서 부드럽게 전환

profile
한줄한줄 기록해보자

0개의 댓글