
Android WearOS 에서는 웨어러블에서 사용이 가능한 WearableRecyclerView 를 지원해주고 있었다.
웨어의 UI를 Compose로 변화하면서 WearableRecyclerView 를 대응하는 ScalingLazyColumn 이 등장하게 되었다. 두둥
compose에서 RecyclerView를 구현하는 건 기존보다 훨씬 더 간단했다! 리싸이클러뷰를 구현할 때 셋트셋트로 구현해야 했던 Adpater 와 Holder를 구현하지 않아도 되기 때문이다.
Jetpack Compose에서 RecyclerView 를 구현하기 위해서는 LazyColum 과 LazyRow 를 사용한다. 이름에서 알 수 있듯이 스크롤별 방향에 따른 두개의 component가 존재한다.
이 글에서는 LazyColum 이 주인공이 아니기 때문에 간단하게 살펴보자!
@Composable
fun composeRecycler(){
val listState = rememberLazyListState() // 스크롤 위치와 항목의 변경사항에 반응하기 위한 상태 저장
LazyColumn(
state = listState,
// 콘텐츠 가장자리에 padding 추가 하기
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp) ,
// 항목 사이의 간격
verticalArrangement = Arrangement.spacedBy(4.dp),
){
// Add a single item
item {
Text(text = "First item")
}
// Add 5 items
items(5) { index ->
Text(text = "Item: $index")
}
// Add another single item
item {
Text(text = "Last item")
}
}
}
스크롤 위치를 저장하기 위한 listState 로 LazyColum 에서 스크롤바나 위치 제어도 할 수 있다.
item 을 사용하여 단일 아이템을 추가하고 items 를 사용하여 람다 형식으로 항목들에 접근하여 추가할 수도 있다. 매개변수로 콘텐츠 가장자리의 패딩과 콘텐츠 사이의 간격을 조절한다.
이렇게 앱에서는 사용되어 왔었는데,,,!
WearOS 의 특징은 app 화면보다 화면이 작아 제한적이라는 점이다.
그래서 Android에서는 Wear OS 용으로 LazyColum 클래스를 확장하여 맞춤형을 만들었다!
다양한 맞춤형 기능들이 존재하고 있다.
그중 가장 큰 기능인! 가독성에 도움을 주기 위해 스크롤이 위로 올라가는 항목에 페이드아웃 효과를 주는 것!
이렇게 기존의 LazyColum 을 WearOS에서 사용하게 되면 작은 화면에서 스크롤이 올라갈 때 짤리는 느낌이 들면서 UI적으로 조금 어색한 느낌이 들지만 ScalingLazyColum 에서는 자연스러운 스크롤 효과가 가능하다.
@Composable
fun UseScalingLazyList(){
val listState = rememberScalingLazyListState()
val contentModifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
val iconModifier = Modifier
.size(24.dp)
.wrapContentSize(align = Alignment.Center)
ScalingLazyColumn(
modifier = Modifier.fillMaxSize(),
state = listState,
autoCentering = AutoCenteringParams(0) // 화면 진입 시 가운데로 배치하
){
item { ButtonExample(contentModifier, iconModifier) }
item { TextExample(contentModifier) }
item { CardExample(contentModifier, iconModifier) }
item { ChipExample(contentModifier, iconModifier) }
item { ToggleChipExample(contentModifier) }
}
}
rememberScalingLazyListState() 는 위와 마찬가지로 ScalingLazyColumn 의 상태를 처리해주기 위해 사용된다.
ScalingLazyColum 은 시각적 효과를 보장하는 기본 설정을 제공하기 때문에 padding verticalArragnetment 와 같은 매개변수를 사용하지 않아도 된다.
그리고 첫번째 아이템으로는 ListHearder 를 사용하면 좋다. ListHearder로 지정된 아이템은 자동으로 autoCentering 속성이 부여된다. 커스텀을 위해 ListHearder 를 사용하지 않는다면 autoCentering 매개변수를 사용하여 수동으로 첫번째 항목에 충분한 패딩을 제공할 수 있다.
Scaffold는 기존 앱에서 사용했었던 다양한 레이아웃들을 사용할 수 있는 발판으로 사용된다.
기존의 앱에서는 FAB 버튼이라던가 bottomBar , AppBar, SnackBar 와 같은 것들을 사용할 수 있었다.

하지만 WearOs 용 Scaffold 함수를 확인해보면 사용할 수 있는 매개변수가 많이 없다.
위에서 언급한 것들을 제공하지 않고 WearOS에 특화된 기능인 vignette timeText positionIndicator pageIndicator 를 제공하고 있다.
Wear 는 시계를 보는 것이 주 목적이기 때문에 어디서든 시계를 표시할 것을 권장하고 있다. ( 대화상자와 같이 소비하는 시간이 짧을 경우 제외 )
해당 클래스를 사용하여 상단에 시계를 노출시킬 수 있다.
timeText = {
TimeText(modifier = Modifier.scrollAway(listState))
}
비네트 효과는 웨어러블 화면의 위쪽과 아래쪽을 흐리게 만드는데 사용되는 화면 장식이다.
vignette = {
Vignette(vignettePosition = VignettePosition.TopAndBottom)
}
우리가 흔히 알고 있는 Wear용 Indicator 이다.
Google Android WearOS 정책이 변경되었는데 전체 뷰가 스크롤되면 스크롤바를 표시해야 한다 라는 권장사항이 추가되었다.

워치에서는 볼륨상태와 스크롤의 상태를 알 수 있게 위에서 언급한 Position Indicator 를 사용하고 있다.
Psition Indicator는 위에서 언급한 대로 Scaffold 에서 사용된다. 하지만 스크롤을 표시해주는 기능을 하고 있으니 기존 우리의 생각으론
🤔 RecyclerView와 셋트이면.. LazyColum 에 사용되야 하는 것이 아닌가..?!
라고 생각할 수 있다!!
질문에 대한 대답은! Indicator 는 시계의 가운데에 위치해야 하기 때문에 Scaffold 에서 사용되야 하는 것이 맞다.

예를 들어 위처럼 플레이리스트 Text 요소 아래 ScalingLazyColum이 있을 수 있다. 그리고 다른것들이 겹쳐서 충분히 표시될 수 있다!! 위에 Text나 다른 요소들이 추가될 수도 있는 것..! 이럴 경우 ScalingLazyColum 레벨에 PositionIndicstor 가 추가된다면 위 그림처럼 가운데에 위치하지 않을 것이다..! 그렇기 때문에 시계의 전체 화면에 표시되어야 하기 때문에 Scaffold 레벨에서 사용되어져야 한다.
Position Indicator는 ScalingLazyListState 이 아닌 ScrollState, LazyListState 과 같은 스크롤옵션에서도 사용할 수 있다. 우리는 ScalingLazyListState 스크롤 옵션을 사용하는 예시를 살펴 볼 것!
@Composable
fun PositionIndicator(
scalingLazyListState: ScalingLazyListState,
modifier: Modifier = Modifier,
reverseDirection: Boolean = false
): Unit
위 함수는 스크롤옵션을 지정하여 사용하는 함수이다. 해당 매개변수들을 지정하여 사용할 수 있다.
@Composable
fun UseScalingLazyList() {
val listState = rememberScalingLazyListState()
val contentModifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp)
val iconModifier = Modifier
.size(24.dp)
.wrapContentSize(align = Alignment.Center)
Scaffold(
timeText = { TimeText(modifier = Modifier.scrollAway(listState)}, // 스크롤하면 시간 삭제 되는 구조) },
vignette = { Vignette(vignettePosition = VignettePosition.TopAndBottom) },
positionIndicator = {
PositionIndicator(
scalingLazyListState = listState,
)
}
) {
ScalingLazyColumn(
modifier = Modifier.fillMaxSize(),
state = listState,
autoCentering = AutoCenteringParams(0) // 화면 진입 시 가운데로 배치하
) {
item { ButtonExample(contentModifier, iconModifier) }
item { TextExample(contentModifier) }
item { CardExample(contentModifier, iconModifier) }
item { ChipExample(contentModifier, iconModifier) }
item { ToggleChipExample(contentModifier) }
}
}
}
ScalingLazyColumn 와 같은 스크롤state 를 공유하여 Scaffold 에 있는 Position Indicator 에서 스크롤의 상태를 알 수 있다!!
스크롤의 상태를 알 기 때문에 스크롤이 될 때만 스크롤바가 생기고 아닐때 보여지지 않는 기능도 있다.
해당 스크롤바를 커스텀 할 수 있는 기능도 있는데 좀 더 살펴보고 글을 적어야겠다.
Goole WearOS Compose 사용을 위한 CodeLab
안녕하세요. 좋은 글 감사합니다. !
"스크롤의 상태를 알 기 때문에 스크롤이 될 때만 스크롤바가 생기고 아닐때 보여지지 않는 기능도 있다."
라고 하셨는데요, 전 별다른건 하지 않았는데, 스크롤바가 항상 보여지진 않고 스크롤이 될때만 나오더라구요. 항상 스크롤바가 나오도록 하거나, 스크롤바의 visibility를 조정하기 위해선 어떻게 해야하는지도 도와주실 수 있으실까요? 공식 개발문서에 찾아봤는데도 찾기가 어렵네요 ㅠ