본문 바로가기
안드로이드/기타

ImageView의 ColorFilter 다중 적용 문제

by 나이아카 2025. 12. 8.
반응형

 ImageView의 Drawable에 ColorFilter를 이용해 tint를 수정하는 방식으로 이미지를 적용하는 코드를 작성했었습니다. 그런데 A 프래그먼트에서 tint를 검은색으로 수정하니 B 프래그먼트에서도 tint가 검은색으로 보이는 이슈가 있더라구요. 이를 해결하고 나니 굉장히 시스템적인 부분이라는 것을 알게 되서 한 번 정리하려고 합니다.


문제의 발달

 처음 이 문제가 발견한 것은, 특정 화면에서 이미지가 보이지 않는 다는 이슈가 들어왔을 때 입니다. Glide를 통해 이미지를 얻어오는데 실패했을 때 발생시키는 PlaceHolder를 Glide의 placeholder 함수가 아니라 ImageView로 직접 처리를 하고 있었는데요. 이 ImageView가 가진 drawable의 tint 값을 setColorFilter를 통해 검은색으로 변경시키는 코드가 A 프래그먼트에 숨어 있었는데, B 프래그먼트를 호출할 때 동일한 drawableId를 사용했고 검은색 배경에 검은색으로 변경된 drawable이 들어가게 되니 아이콘이 보이지 않아 마치 visibility가 GONE으로 떨어진 것 처럼 보이는 문제가 발생했었습니다.

1. A 프래그먼트에서 drawable(R.drawable.icon)을 setColorFilter를 통해 black으로 변경

2. B 프래그먼트에서 A 프래그먼트 호출 후 동일 drawable(R.drawable.icon)을 ImageView에 적용시 black 색상으로 표시(배경이 검정색)

 

문제 해결 순서

 처음에는 당연히 B 프래그먼트에는 visibility를 수정하는 코드가 있고 tint를 적용하는 코드는 없어서 레이아웃이 노출되지 않는 버그라고 인식했습니다. 그러나 개발자 옵션에서 레이아웃 표시를 통해 확인해보니 레이아웃은 정상적으로 노출되고 있음을 확인할 수 있었죠.

 그래서 아이콘의 visibility 조건을 제거하고(무조건 VISIBLE 상태 고정) 확인해봤는데 어쩔땐 보이고 어쩔땐 안보이는 현상이 발생했습니다. 이때 같이 현상을 확인하는 동료분이 바로 B 프래그먼트를 호출하는 플로우에서는 잘 보인다는 얘기를 해주셔서 바로 B 프래그먼트를 호출하는 플로우와 A 프래그먼트를 거쳐 B 프래그먼트를 호출하는 플로우를 따라가며 비교했습니다.

 이후 플로우상 tint 변경 코드를 발견하고, background을 바꿔서 테스트해본 결과 icon의 색상이 바뀐 것 때문에 보이지 않는 것을 확인했고, A 프래그먼트에서 호출하는 setColorFilter를 주석처리하니 정상적으로 동작하는 것을 확인했습니다.

 이제 문제는 A 프래그먼트에서 수정한 icon의 색상이 왜 B 프래그먼트에서도 적용이 되는지 원인을 몰랐지만, 이는 Claude의 도움을 받아 해결할 수 있었습니다. 원인과 해결방안은 아래에서 이어서 설명할게요!

 

원인 및 해결방안

원인

 안드로이드에서 사용하는 리소스는 ResourcesImpl 내부에 캐싱된다고 합니다. 그러니까 한 번 메모리에 올라온 리소스는 캐싱되어서 다시 사용할때는 이미 메모리에 올라와있는 리소스를 재활용하게 되는 것이죠. 제 코드에서 발생한 문제는, 캐싱된 데이터가 살아있는 동안 tint를 변경한 인스턴스를 그대로 다시 호출했다는 점인데요. 하나의 리소스는 내부 구현(단말이나 안드로이드 버전에 따라서 조금씩 달라지는)에 따라 하나의 인스턴스를 지니고 있고, 이 인스턴스의 tint를 변경하게 되면 이 인스턴스를 호출하는 모든 View에 영향을 주게 됩니다. 

 

해결 방안

 해결 방안은 비교적 간단합니다. 문제는 인스턴스가 캐싱되어 하나를 돌려쓰고 있다는 점인데요. 이를 해결하기 위해 mutate() 메소드를 활용해 리소스를 복사해 그 클래스에서만 사용할 또 다른 인스턴스를 생성하면 된다는 것입니다. 이 mutate()로 만든 객체는 또 다른 캐싱 데이터를 생성하는 것이 아니고 연결된 클래스가 사라지면 같이 사라지는 지역변수이기 때문에 여러모로 건드려도 문제가 없습니다. 그러니 tint를 건드리는 경우 mutate() 메소드를 활용하는 것이 좋습니다.

 

참조

https://stackoverflow.com/questions/10889415/adding-a-color-filter-to-a-drawable-changes-all-buttons-using-the-same-drawable?

 

Adding a color filter to a Drawable changes all Buttons using the same Drawable

I have a screen where multiple Buttons use the same background Drawable. I have reusable code I use in various projects to add an OnTouch listener that adds a gray color filter while a button is be...

stackoverflow.com

https://stackoverflow.com/questions/6018602/statelistdrawable-to-switch-colorfilters

 

StateListDrawable to switch colorfilters

I want to create custom buttons to use in a TabHost. I haven been trying to just use the same image resource (png), but have the colorfilter change depending on the state. So I made this bit to ser...

stackoverflow.com

 


 분명 금방 해결될거라는 생각으로 저한테 할당된 일이었는데, 생각지도 못한 이슈로 인해서 한참이나 찾아헤맸던 오류입니다. Cluade Code랑 ChatGPT(Codex)도 이 부분을 찾아내지 못해서 결국 AI 도움 없이 찾아보면서 해결하게 되었는데요. 그나마 다행인 건 여러 테스트를 진행해본 결과 Tint 값이 어디선가 수정되고 있다는 의심을 할 수 있어서 겨우 찾아냈습니다. 근데 이 건을 해결하고 AI에게 물어보니 단박에 찾아주더군요. 역시 질문도 알아야 제대로 할 수 있다는게... 이런 오류를 겪어봤어야 프롬프트를 좀 더 잘 적을 수 있네요(떨어지는 프롬프트 작성 능력 ㅠㅠ).

 뭐, 그래도 이런 문제가 발생하면 다음에는 바로 찾을 수 있으니 다행이라고 생각합니다. 까먹기 전에 기록은 필수라고 생각하고요.

댓글