구성 팔레트

이 기사에서는 거의 알려지지 않은 Jetpack 라이브러리인 Palette를 보여 드리겠습니다. android.graphics.Bitmap 에서 중요한 색상을 추출할 수 있습니다. Jetpack Compose 앱에서 Palette가 생성한 데이터를 사용합니다. 시작 직후 앱의 모습은 다음과 같습니다.



FAB를 클릭한 후 사용자는 이미지를 선택할 수 있습니다. 그러면 앱이 다음과 같이 표시됩니다.



나쁘지 않죠? 소스 코드를 살펴보겠습니다. GitHub에서 찾을 수 있습니다.

비트맵 로드 및 팔레트 가져오기



Jetpack Palette를 사용하려면 구현 종속성에 추가해야 합니다.

implementation 'androidx.palette:palette-ktx:1.0.0'


다음으로 액티비티를 살펴보겠습니다.

class PaletteDemoActivity : ComponentActivity() {

  private lateinit var viewModel: PaletteDemoViewModel

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    viewModel = ViewModelProvider(this).get(PaletteDemoViewModel::class.java)
    setContent {
      PaletteDemoTheme {
        Surface(color = MaterialTheme.colors.background) {
          PaletteDemo(
            onClick = { showGallery() }
          )
        }
      }
    }
  }
  


멋진 아키텍처를 얻기 위해 ViewModel 를 사용합니다. 활동에서 viewModel가 사용된 위치를 곧 확인할 수 있습니다. 하지만 먼저 PaletteViewModel를 살펴보겠습니다.

class PaletteDemoViewModel : ViewModel() {

  private val _bitmap: MutableLiveData<Bitmap> =
    MutableLiveData<Bitmap>()

  val bitmap: LiveData<Bitmap>
    get() = _bitmap

  fun setBitmap(bitmap: Bitmap) {
    _bitmap.value = bitmap
  }

  private val _palette: MutableLiveData<Palette> =
    MutableLiveData<Palette>()

  val palette: LiveData<Palette>
    get() = _palette

  fun setPalette(palette: Palette) {
    _palette.value = palette
  }
}


따라서 비트맵과 팔레트라는 두 가지 속성이 있습니다. 둘 다 활동 내부에서 설정되고 구성 가능한 함수 내부에서 사용됩니다. PaletteDemo()는 구성 가능한 계층 구조의 루트입니다. showGallery() 를 호출하는 람다 식을 받습니다. 이 함수가 하는 일은 다음과 같습니다.

private fun showGallery() {
  val intent = Intent(Intent.ACTION_PICK)
  intent.type = "image/*"
  val mimeTypes =
    arrayOf("image/jpeg", "image/png")
  intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
  startActivityForResult(intent, REQUEST_GALLERY)
}

startActivityForResult()ComponentActivity에서 더 이상 사용되지 않기 때문에 교체해야 하지만 향후 문서를 위해 저장해 두겠습니다. 😎 다음은 흥미로운 부분입니다. 사용자가 이미지를 선택하면 어떻게 됩니까?

override fun onActivityResult(requestCode: Int, 
                              resultCode: Int, 
                              intent: Intent?) {
  super.onActivityResult(requestCode, resultCode, intent)
  when (requestCode) {
    REQUEST_GALLERY -> {
      if (resultCode == RESULT_OK) {
        intent?.let {
          it.data?.let { uri ->
            val source = ImageDecoder.createSource(
              contentResolver,
              uri
            )
            val bitmap = ImageDecoder.decodeBitmap(source).asShared()
            viewModel.setBitmap(bitmap)
            lifecycleScope.launch {
              viewModel.setPalette(
                Palette.Builder(bitmap).generate()
              )
            }
          }
        }
      }
    }
  }
}


비트맵을 얻으려면 먼저 ImageDecoder.createSource()를 사용하여 소스를 만듭니다. 그런 다음 소스가 ImageDecoder.decodeBitmap()로 전달됩니다. asShared()를 발견했습니까? 내부 Jetpack 팔레트getPixels()가 호출됩니다. 이 메서드는 IllegalStateException: unable to getPixels(), pixel access is not supported on Config#HARDWARE bitmaps 와 함께 실패할 수 있습니다. asShared()는 이를 방지합니다. 문서say:

Return an immutable bitmap backed by shared memory which
can be efficiently passed between processes via Parcelable.

If this bitmap already meets these criteria it will return
itself.



이 메서드는 API 레벨 31에서 도입되었으므로 이전 플랫폼을 지원하려면 비슷한 것으로 교체해야 합니다.

코드로 돌아갑니다. 비트맵에서 중요한 색상을 어떻게 얻습니까? 먼저 Palette.Builder 인스턴스를 만들어 비트맵을 전달합니다. 그런 다음 이 개체에서 generate()를 호출합니다. 이 동기식 버전 외에도 AsyncTask 기반 변형도 있습니다. 보시다시피 저는 대신 코루틴을 사용하기로 했습니다.

이제 구성 가능한 기능PaletteDemo()을 살펴보겠습니다.

팔레트 구성



대부분의 Compose 앱은 루트로 Scaffold() 를 가지며 여기에는 TopAppBar() 및 제 예와 같이 FloatingActionButton() 가 포함될 수 있습니다. 내 콘텐츠 영역은 세로로 스크롤 가능한 영역Column()으로 구성되어 있으며 이미지를 선택할 때까지 비어 있습니다. 그런 다음 중요한 색상을 나타내는 Image() 및 여러 Box() 요소를 포함합니다.

@Composable
fun PaletteDemo(
  viewModel: PaletteDemoViewModel = viewModel(),
  onClick: () -> Unit
) {
  val bitmap = viewModel.bitmap.observeAsState()
  val palette = viewModel.palette.observeAsState()
  Scaffold(topBar = {
    TopAppBar(title = { Text(stringResource(id =
                                 R.string.app_name)) })
  },
    floatingActionButton = {
      FloatingActionButton(onClick = onClick) {
        Icon(
          Icons.Default.Search,
          contentDescription =
              stringResource(id = R.string.select)
        )
      }
    }
  ) {
    Column(
      modifier = Modifier
        .verticalScroll(rememberScrollState())
        .padding(16.dp),
      horizontalAlignment = Alignment.CenterHorizontally
    ) {
      bitmap.value?.run {
        Image(
          bitmap = asImageBitmap(),
          contentDescription = null,
          alignment = Alignment.Center
        )
      }
      palette.value?.run {
        swatches.forEach {
          Box(
            modifier = Modifier
              .padding(top = 8.dp)
              .fillMaxWidth()
              .height(32.dp)
              .clip(RectangleShape)
              .background(Color(it.rgb))
          )
        }
      }
    }
  }
}


변경 사항에 대한 정보를 얻으려면 observeAsState()bitmap 모두에서 palette를 호출해야 합니다. asImageBitmap() ? 비트맵을 ImageBitmap 로 변환합니다.

예를 들어 Palette 또는 getVibrantColor() 와 같이 getDarkVibrantColor() 인스턴스에서 호출할 수 있는 몇 가지 메서드가 있습니다. 내 코드는 swatches 목록을 반복합니다. 자세한 내용은 설명서를 참조하십시오.

결론



Composable 앱 내에서 Jetpack Palette를 사용하는 것은 쉽고 재미있습니다. Material You 이후 라이브러리가 업데이트를 받는지 확인하는 것은 흥미로울 것입니다. 이 게시물이 마음에 드셨기를 바랍니다. 댓글로 여러분의 생각을 공유해주세요.

좋은 웹페이지 즐겨찾기