본문 바로가기

Android

(160)
[Compose] Compose 1.11 Grid API로 2차원 레이아웃 구현해보기 최근 안드로이드 공식 블로그를 확인하다가, Compose Foundation에 CSS Grid를 닮은 새로운 Grid API가 추가됐다는 소식을 알게 되었다. 지금까지 Compose에서 격자 형태 레이아웃을 짜려면 LazyVerticalGrid나 직접 Layout을 구현하는 방법밖에 없었는데, 트랙 기반으로 2차원 배치를 선언할 수 있다는 점이 흥미로워서 직접 하나씩 만들어보며 정리해보고자 한다.Grid API란CSS Grid에서 착안한 2차원 레이아웃이다. 열(column)과 행(row) 트랙을 먼저 선언하고, 자식을 그 격자에 배치하는 방식이다. 다만 LazyVerticalGrid와 이름이 비슷해서 헷갈리기 쉬운데, Grid는 스크롤 컨테이너가 아니라 일반 레이아웃이다. 자식이 화면 밖으로 나가도 전..
[Compose] Modifier 순서에 따라 결과가 달라지는 이유 - padding, border, clickable, size 비교 Compose로 UI를 작성하다 보면, 분명히 똑같은 modifier들을 썼는데 화면에 그려지는 결과가 다르게 나오는 경우를 종종 마주친다. 필자도 처음 Compose를 접했을 때 padding과 background 순서를 바꿔가며 한참 헤맨 기억이 있는데, 최근 다시 이 주제를 정리할 기회가 생겨 자주 헷갈리는 네 가지 조합을 직접 비교해보며 정리해보고자 한다.Modifier는 순서대로 적용된다Compose의 Modifier 체인은 왼쪽(위)에서 오른쪽(아래) 순서로 그대로 적용된다. 문제는 같은 modifier들을 어떤 순서로 나열하느냐에 따라 layout(크기·위치), drawing(그리는 영역), 입력 처리(클릭 가능 영역) 결과가 전부 달라질 수 있다는 점이다. 말로만 들으면 당연해 보이는데, ..
[Compose] TextField Max Length 숨겨진 버그 - InputTransformation이 프로그래매틱 변경을 놓치는 이유 최근 안드로이드 관련 블로그를 보다가, Jetpack Compose의 새로운 BasicTextField(TextFieldState API)에서 maxLength 제한이 특정 상황에서는 전혀 동작하지 않는다는 글(A Hidden Problem in Jetpack Compose TextField Max Length)을 알게 되었다. 필자도 최근 프로젝트에서 TextFieldState API로 마이그레이션하며 InputTransformation.maxLength()를 자주 쓰고 있던 터라, 남 일 같지가 않아서 직접 재현해보며 원인을 정리해보고자 한다.InputTransformation.maxLength()는 어디에 걸려있나새로운 BasicTextField(state = ...) API는 TextFieldSt..
[Compose] LazyColumn contentType 함정 - 한 줄짜리 코드가 메모리를 갉아먹는 이유 최근 외부 기술 블로그를 살펴보다가, LazyColumn의 contentType 파라미터에 아이템마다 다른 값을 넘겼다가 스크롤할수록 메모리가 계속 늘어나는 문제를 겪었다는 사례(The One-Liner That Was Eating Our Memory)를 접하게 되었다. 한 줄짜리 코드가 메모리 누수로 이어진다는 점이 흥미로워서, 필자도 직접 ComposeSample 프로젝트에 재현 예제를 만들어보며 원인을 파헤쳐보았다.이번 글에서는 LazyColumn의 재사용 풀(reuse pool)이 어떻게 동작하는지, 그리고 contentType을 잘못 지정했을 때 왜 메모리가 회수되지 않는지 실측 코드와 함께 정리해보고자 한다.LazyColumn은 화면 밖 아이템을 어떻게 처리할까LazyColumn은 화면 밖으로..
[Compose] LocalContext.current.getString()은 안티패턴 - stringResource와 UiText 패턴으로 올바르게 해결하기 필자가 회사에서 업무를 진행하다 보면, Compose 화면에서 문자열을 가져오기 위해 다음과 같이 LocalContext.current.getString()을 사용하는 코드를 종종 보게 된다.@Composable fun MyScreen() { val context = LocalContext.current val title = context.getString(R.string.screen_title) Text(text = title) } 필자도 처음 Compose를 접했을 때는 저 방식이 익숙해서 자주 썼었는데, 최근 안드로이드 관련 기술 블로그를 확인하다가 "Why You Shouldn't Use LocalContext for Strings" 글을 보게 되었고, 이 패턴이 ..
[Compose] 스피닝 휠에 모션 블러를 적용해보자 - Ghost Frames, BlurMaskFilter, RenderEffect 비교 필자가 회사에서 업무를 진행하다 보면, 룰렛이나 럭키 드로우 같은 회전 UI를 만들어야 하는 경우가 가끔 있다. 단순히 빙글빙글 돌기만 하면 좀 밋밋한데, 빠르게 회전할 때 살짝 흐릿하게 처리되는 모션 블러가 더해지면 한층 더 그럴듯한 느낌이 난다는 것을 게임이나 시뮬레이션 앱을 보면서 느낀 적이 있다.막상 Compose에서 모션 블러를 어떻게 구현해야 하는지는 의외로 잘 정리된 자료를 찾기가 어려웠다. 최근 안드로이드 관련 기술 블로그를 확인하다가 ProAndroidDev의 "Motion Blur for a Spinning Wheel in Jetpack Compose" 글을 보게 되었고, 세 가지 다른 접근 방식을 비교하는 형태로 정리되어 있어서 매우 인상 깊었다.이번 글에서는 Jetpack Compo..
[Compose] iOS의 Large Content Viewer를 안드로이드에 - 접근성을 고려한 아이콘 프리뷰 구현 필자가 회사에서 업무를 진행하다 보면, 하단 네비게이션이나 툴바의 작은 아이콘들이 디자인적으로 깔끔하긴 한데 시력이 약한 사용자에게는 잘 안 보일 수 있겠다는 생각이 들 때가 있다. 특히 아이콘은 시스템 폰트 크기 설정과 무관하게 그려지기 때문에, Dynamic Type만으로는 보완이 잘 안 되는 부분이다.iOS에서는 이런 문제를 위해 Large Content Viewer라는 기능을 제공한다. 작은 아이콘이나 버튼을 길게 누르면 확대된 프리뷰가 화면에 표시되어, 어떤 아이콘인지 확인할 수 있도록 도와준다.최근 안드로이드 관련 기술 블로그를 확인하다가 eevis.codes의 "Adding navigation support to Large Content Viewer with Compose" 글을 보게 되었고..
[Compose] 스티커 캔버스를 만들어보자 - 복합 제스처, Spring 물리, 필오프(Peel-Off) 애니메이션 필자가 회사에서 업무를 진행하다 보면, 포토 에디터나 화이트보드 같은 화면에서 사용자가 직접 요소를 자유롭게 배치하고 크기·각도를 조절할 수 있는 UI가 필요한 경우가 종종 있다. 카카오톡 사진 편집기의 스티커 기능이나 인스타그램 스토리의 텍스트 배치를 떠올리면 익숙한 형태이다.막상 직접 구현해 보려고 하면 드래그·핀치·회전이 동시에 일어나는 복합 제스처를 다뤄야 하고, 거기에 자연스러운 물리 애니메이션까지 얹어야 해서 진입 장벽이 꽤 높게 느껴진다.최근 안드로이드 관련 기술 블로그를 확인하다가 Adit Lal의 "Building StickerExplode Part 1" 글을 보게 되었고, 거기서 다루는 패턴을 정리해 두면 실무에서 응용하기 좋겠다는 생각이 들었다.이번 글에서는 Jetpack Compos..