이동식 저장소

Compose Example - sunflower 본문

Primary/Compose

Compose Example - sunflower

해스끼 2022. 8. 2. 14:38

Google의 샘플 앱 중 하나인 sunflower를 간단히 리뷰한다. ``compose`` 브랜치 기준으로 작성하였다.

 

GitHub - android/sunflower: A gardening app illustrating Android development best practices with Android Jetpack.

A gardening app illustrating Android development best practices with Android Jetpack. - GitHub - android/sunflower: A gardening app illustrating Android development best practices with Android Jetp...

github.com


이미지 로딩

비동기적으로 이미지를 가져오려면 Glide나 Coil 등의 외부 라이브러리를 사용해야 한다. Sunflower에서는 Compose를 지원하는 Coil을 이용하여 이미지를 로딩하였다.

@Composable
private fun PlantImage(
    imageUrl: String,
    imageHeight: Dp,
    modifier: Modifier = Modifier,
    placeholderColor: Color = MaterialTheme.colors.onSurface.copy(0.2f)
) {
    val painter = rememberAsyncImagePainter(
        model = ImageRequest.Builder(LocalContext.current)
            .data(data = imageUrl)
            .crossfade(true)
            .build()
    )

    Image(
        painter = painter,
        contentScale = ContentScale.Crop,
        contentDescription = null,
        modifier = modifier
            .fillMaxWidth()
            .height(imageHeight)
    )

    if (painter.state is AsyncImagePainter.State.Loading) {
        Box(
            modifier = Modifier
                .fillMaxSize()
                .background(placeholderColor)
        )
    }
}

전형적인 이미지 로딩 코드이다. 아래 부분의 ``if``문은 이미지가 로딩 중일 때 보여줄 placeholder를 구현한 것이다. Placeholder도 여러 방법으로 구현할 수 있는데, Accompanist의 ``Modifier.placeholder`` 함수를 사용할 수도 있고, ``ImageBuilde.placeholder(drawableId)`` 함수를 사용해도 된다.

 

난 Accompanist를 애용하는 편이다.

애니메이션

@Composable
fun PlantDetails(
    plant: Plant,
    isPlanted: Boolean,
    callbacks: PlantDetailsCallbacks,
    modifier: Modifier = Modifier
) {
    // animated float value
    val toolbarAlpha = transition.animateFloat(
        transitionSpec = { spring(stiffness = Spring.StiffnessLow) }, label = ""
    ) { toolbarTransitionState ->
        if (toolbarTransitionState == ToolbarState.HIDDEN) 0f else 1f
    }

    Box(
        Modifier
            .fillMaxSize()
            .systemBarsPadding()
            // attach as a parent to the nested scroll system
            .nestedScroll(nestedScrollConnection)
    ) {
        // toolbar alpha is animated
        PlantToolbar(
            toolbarState, plant.name, callbacks,
            toolbarAlpha = { toolbarAlpha.value },
            contentAlpha = { contentAlpha.value }
        )
    }
}

``toolbarAlpha``는 ``transition``의 값에 따라 animate되는 Float 값이다. 값이 animate되므로 툴바의 투명도가 부드럽게 전환된다.

Dimension 처리

Sunflower는 View System와 Compose를 함께 사용하고 있다. 따라서 string이나 dimension value 등의 리소스를 두 시스템 모두에서 사용할 수 있게 해야 한다.

 

View System에서는 xml에 값을 바로 사용하면 되고, Compose에서는 ``dimensionResource(id)`` 함수를 이용하면 값을 불러올 수 있다. 자주 사용하는 값이라면 다음 코드처럼 별도의 상수로 선언해도 된다.

object Dimens {

    val PaddingSmall: Dp
        @Composable get() = dimensionResource(R.dimen.margin_small)

    val PaddingNormal: Dp
        @Composable get() = dimensionResource(R.dimen.margin_normal)

    val PaddingLarge: Dp = 24.dp

    val PlantDetailAppBarHeight: Dp
        @Composable get() = dimensionResource(R.dimen.plant_detail_app_bar_height)

    val ToolbarIconPadding = 12.dp

    val ToolbarIconSize = 32.dp
}

``get()``에 ``@Composable``이 붙은 이유는 ``dimensionResource`` 함수가 ``@Composable``이기 때문이다.

 

순수 Compose 앱에서도 리소스 xml 파일을 유용하게 사용할 수 있다. 화면 크기 또는 locale에 따라 다른 값을 적용해야 하는 경우에도 쉽게 대응할 수 있기 때문이다.

 

레이아웃은 몰라도 리소스 정도는 xml로 작성해줄 수 있지 ㅎㅎ

Comments