(Part 2) Jetpack Compose와 Glide, Coil, Fresco 이미지 로딩 라이브러리 Landscapist

skydoves·2022년 2월 19일
2
post-thumbnail

본 포스트는 총 2부로 연재되며, 아래에서 1부 포스트를 확인하실 수 있습니다.

지난번 1부 포스트에서는 차세대 UI 툴킷인 Jetpack Compose와 오픈소스 라이브러리 landscapist의 기본적인 사용 방법에 대하여 살펴보았습니다.

이번 포스트에서는 landscapist를 활용하여 다음과 같은 기능들을 구현합니다.

  • Custom Composables: 이미지 로드 상태(성공/실패/로딩)에 따라 custom composable을 빌드합니다.
  • Preview on Android Studio: Android Studio에서 미리보기를 구현합니다.
  • Shimmer Effect: 이미지 로드 시 Shimmering Effect를 구현합니다.
  • Circular Reveal Animation: 이미지를 그릴 때 circular reveal 애니메이션을 구현합니다.
  • Palette: 이미지로 부터 primary color 정보를 추출합니다.

이번 포스트에서 소개 드리는 기능들은 Glide, Coil, Fresco 모두 동일하게 구현이 가능합니다. 예시에서는 Glide의 사례로 통일하였습니다.

Custom Composables

landscapist는 다음과 같이 이미지 로드 상태에 따라 UI 커스터마이징이 가능하도록 custom composable 옵션을 제공합니다.

  • loading: 이미지 로드 시 실행되는 composable 함수.
  • success: 이미지 로드가 모두 끝나고 실행되는 composable 함수.
  • failure: 이미지 로드에 실패했을 때 실행되는 composable 함수.

아래의 예시는 이미지 로드 시 CircularProgressIndicator를 노출하고, 이미지 로드에 실패한 경우 에러 텍스트를 노출합니다. 이미지 로드에 성공한 경우에는 정상적으로 이미지가 노출됩니다.

 GlideImage( // CoilImage, FrescoImage
   imageModel = imageUrl,
   modifier = modifier,
   // shows an indicator while loading an image.
   loading = {
     ConstraintLayout(
       modifier = Modifier.fillMaxSize()
     ) {
       val indicator = createRef()
       CircularProgressIndicator(
         modifier = Modifier.constrainAs(indicator) {
           top.linkTo(parent.top)
           bottom.linkTo(parent.bottom)
          start.linkTo(parent.start)
          end.linkTo(parent.end)
         }
       )
     }
   },
   // shows an error text if fail to load an image.
   failure = {
     Text(text = "image request failed.")
   })

다음은 이미지 로드 - 로드 실패가 순차적으로 실행되는 예시 화면입니다.

아래의 예시와 같이 이미지 로드에 성공한 경우에도, 제공되는 imageBitmap을 사용하여 커스터마이징을 할 수 있습니다.

GlideImage( // CoilImage, FrescoImage
  imageModel = imageUrl,
  // draw a resized image.
  success = { imageState ->
    imageState.imageBitmap?.let {
      Image(
        bitmap = it,
        modifier = Modifier
          .width(128.dp)
          .height(128.dp))
    }
  },
  loading = { 
    // do something 
  })

Preview on Android Studio

Jetpack Compose의 강력한 기능 중 하나는, 앱 전체를 빌드 하지 않아도 stateless한 Compose 함수에 대하여 부분적인 빌드가 가능하고, Android Studio에서 미리 보기를 할 수 있다는 것입니다. landscapist 역시 @Preview annotation과 previewPlaceholder parameter를 사용하여 미리보기를 구현할 수 있습니다.

GlideImage(
  imageModel = poster.poster,
  modifier = Modifier.aspectRatio(0.8f),
  previewPlaceholder = R.drawable.poster
)

아래의 예시와 같이 Preview 테스트를 해볼 수 있습니다.

@Preview
@Composable
private fun PreviewImage() {
  GlideImage(
    imageModel = null,
    modifier = Modifier.aspectRatio(0.8f),
    previewPlaceholder = R.drawable.poster
  )
}

다음은 Android Studio에서 preview 빌드를 완료한 결과입니다.

Shimmer Effect

Shimmering 효과는 데이터를 로딩하는 동안 웨이브(wave) 애니메이션을 제공함으로써 사용자들에게 보다 나은 UX 경험을 제공할 수 있는 방법입니다.

landscapist에서는 이미지를 로딩 하는 동안 shimmer effect를 구현할 수 있는 기능을 제공합니다. 아래의 예시와 같이 ShimmerParams를 사용하여 shimmer effect를 커스텀 화 할 수 있습니다.

GlideImage( // CoilImage, FrescoImage
   imageModel = imageUrl,
   modifier = modifier,
   // shows a shimmering effect when loading an image.
   shimmerParams = ShimmerParams(
       baseColor = MaterialTheme.colors.background,
       highlightColor = shimmerHighLight,
       durationMillis = 350,
       dropOff = 0.65f,
       tilt = 20f
     ),
   // shows an error text message when request failed.
   failure = {
     Text(text = "image request failed.")
   })

다음은 이미지 로딩 중 Shimmer Efeect가 적용된 결과입니다.

Circular Reveal Animation

Circular reveal은 UI를 조금 더 역동적으로 보여주고, 사용자들에게 몰입감을 더해줄 수 있는 애니메이션입니다. landscapist에서는 아래와 같이 CircularReveal parameter를 사용하여 구현할 수 있습니다.

GlideImage( // CoilImage, FrescoImage
  imageModel = imageUrl,
  // Crop, Fit, Inside, FillHeight, FillWidth, None
  contentScale = ContentScale.Crop,
  // shows an image with the circular reveal animation.
  circularReveal = CircularReveal(duration = 350),
  // shows a placeholder ImageBitmap when loading.
  placeHolder = ImageBitmap.imageResource(R.drawable.placeholder),
  // shows an error ImageBitmap when the request failed.
  error = ImageBitmap.imageResource(R.drawable.error)
)

다음은 이미지를 그릴 때 Circular Reveal 애니메이션이 적용된 결과입니다.

Palette

landscapist는 이미지로부터 대표 색상 정보(primary color profiles)를 추출할 수 있는 기능을 제공합니다. Material Design에 기반한 color profiles은 Extract color profiles를 참고바랍니다.

아래의 예시와 같이 BitmapPalette parameter를 사용하여 동적으로 대표 색상 정보를 추출할 수 있습니다.

var palette by remember { mutableStateOf<Palette?>(null) }

GlideImage( // CoilImage, FrescoImage also can be used.
  imageModel = imageUrl,
  bitmapPalette = BitmapPalette {
    palette = it
  }
)

Crossfade(
  targetState = palette,
  modifier = Modifier
    .padding(horizontal = 8.dp)
    .size(45.dp)
) {
  Box(
    modifier = Modifier
      .background(color = Color(it?.lightVibrantSwatch?.rgb ?: 0))
      .fillMaxSize()
  )
}

다음 화면은 각 이미지 별로 primary color 정보를 추출한 결과입니다.

Conclusion

이번 포스트에서는 Jetpack Compose 이미지 로딩 라이브러리 landscapist에서 제공하는 추가적인 기능들에 대하여 살펴보았습니다.

landscapist는 Jetpack Compose 초기 alpha02 버전부터 개발되어, 현재까지 약 50번의 release와 다양한 이슈가 해결되며 발전되어 왔습니다. Jetpack Compose를 도입하기 시작하는 회사가 점점 증가함에 따라 landscapist의 사용 사례도 점차 늘어나고 있고, 글로벌한 환경에서도 잘 동작하기 위한 견고한 라이브러리로 거듭 성장하고 있습니다.

landscapist를 사용하면서 이슈를 마주하신다면 언제든지 issues에 제보를 부탁드립니다. 또한, 회사 제품에서의 사용 사례를 Pull Request로 기여해 주시는 것도 언제든지 환영입니다.

언제나 즐거운 코딩 되시길 바랍니다!

작성자 엄재웅 (skydoves)

profile
http://github.com/skydoves

0개의 댓글