본문 바로가기

안드로이드

Compose 1.1 정리

반응형

https://android-developers.googleblog.com/2022/02/jetpack-compose-11-now-stable.html

 

Jetpack Compose 1.1 is now stable!

Posted by Florina Muntenescu , Android Developer Relations Engineer Today, we’re releasing version 1.1 of Jetpack Co...

android-developers.googleblog.com


1. Image vector caching

벡터 이미지에 대한 캐싱이 추가되었다.

Compose 1.0 의 PainterResource 코드

val imageVector = remember(path, id) {
    loadVectorResource(context.theme, res, id)
}

private fun loadVectorResource(theme: Resources.Theme, res: Resources, id: Int): ImageVector {
    @Suppress("ResourceType") val parser = res.getXml(id)
    if (parser.seekToStartTag().name != "vector") {
        throw IllegalArgumentException(errorMessage)
    }
    return loadVectorResourceInner(theme, res, parser)
}

imageVector 에 대해서 remember 만을 이용하여 재사용 처리를 하고 있다.

Compose 1.1 의 PainterResource 코드

val imageVector = loadVectorResource(context.theme, res, id)
rememberVectorPainter(imageVector)

@Composable
private fun loadVectorResource(
    theme: Resources.Theme,
    res: Resources,
    id: Int
): ImageVector {
    val imageVectorCache = LocalImageVectorCache.current
    val key = ImageVectorCache.Key(theme, id)
    var imageVectorEntry = imageVectorCache[key]
    if (imageVectorEntry == null) {
        @Suppress("ResourceType") val parser = res.getXml(id)
        if (parser.seekToStartTag().name != "vector") {
            throw IllegalArgumentException(errorMessage)
        }
        imageVectorEntry = loadVectorResourceInner(theme, res, parser)
        imageVectorCache[key] = imageVectorEntry
    }
    return imageVectorEntry.imageVector
}

 

1.1 버전에서는 remember 부분이 제거되고 ImageVectorCache 가 추가되어 캐싱을 별도로 진행한다.
캐싱된 벡터이미지는 configuration 이 변경될 경우 초기화된다.

2. Touch target sizing

터치가 가능한 메테리얼 컴포넌트에 대해서 최소 터치 영역만큼 크기를 보정해주는 기능이 추가되었다.

Compose 1.0

별다른 설정없이 컴포넌트를 추가한 경우 위와 같이 화면에 표시된다.

IconButton 에서는 최소 크기를 조정하는 Modifier 가 존재해서 IconButton 만 크기가 조절된다.

Compose 1.1 (좌) on (우) off


Compose 1.1 에서는 IconButton 에 최소 크기를 조정해주던 Modifier 가 빠졌고,
전부 MinimumTouchTargetEnforcement 로 대체되었다.

 

그래서 왼쪽처럼 모든 컴포넌트가 터치영역만큼 크기가 변경되는 걸 확인 할 수 있다.

아래 코드를 통해서 해당 기능을 끌 수 있으며 오른쪽 처럼 표시된다.

CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides false) {
…
}

Compose 1.0 -> 1.1 로 갈때 IconButton 을 제외한 다른 컴포넌트들의 위치나 크기가 틀어질 수 있으니 확인이 필요하다.

 

CompositionLocalProvider(LocalMinimumTouchTargetEnforcement provides true) { 
    Checkbox(modifier = Modifier.size(10.dp), checked = true, onCheckedChange = {}) 
}

만약 컴포넌트에 사이즈를 직접 지정한 경우에는 MinimumTouchTargetEnforcement 로 인한 크기 조정은 동작하지 않는다.

3. Experimental to stable APIs

  • Animation related APIs, such as: EnterTransition, ExitTransition, some of the AnimatedVisibility APIs,
  • Vector related APIs: rememberVectorPainter, VectorProperty, VectorConfig, and RenderVectorGroup

와 같은 몇가지 함수가 실험기능에서 정식기능으로 포함되었다.

4. New experimental APIs

1. AnimatedContent can now be saved and restored when using rememberSaveable.
-> AnimatedContent 에 공통된 화면으로 전환시에 데이터 보존을 위해 contentKey 를 지정할 수 있다.

enum class Type(val key: Int) {
  A(1), B(1), C(2)
}

Column {
  var type by remember { mutableStateOf(Type.A) }

  Row() {
    Type.values().forEach {
      Button(onClick = { type = it }) {
        Text(it.name + " Key(${it.key})")
      }
    }
  }

  Text(text = "with Key")

  val transition = updateTransition(targetState = type, label = "target")
  transition.AnimatedContent(contentKey = { it.key }) {
    val random by remember { mutableStateOf(Random.nextInt()) }

    Column() {
      Text(text = "Current Type $type")
      Text(text = random.toString())
    }
  }

  Text(text = "without Key")

  transition.AnimatedContent() {
    val random by remember { mutableStateOf(Random.nextInt()) }

    Column() {
      Text(text = "Current Type $type")
      Text(text = random.toString())
    }
  }
}

 


키가 동일한 A,B 와 키가 다른 C 3가지 Type이 있다.

 

contentKey 를 지정한 AnimatedContentType 이 변경되어도 contentKey 가 동일한 경우에는 실제 변경이 있는 영역만 변경이 된다.


지정하지 않은 경우에는 Type 변경되면 무조건 content를 영역을 새로 그리는 걸 볼 수 있다.

 

2. LazyColumn/LazyRow item positions can be animated using Modifier.animateItemPlacement().
-> 1.1 에서 animateItemPlacement() 를 추가되면서 리스트 아이템에 대한 애니메이션 가능하게되었다.
Key 를 기반으로 하기때문에 적절한 key 를 지정해주어야 원하는 애니메이션이 동작한다.

val list = remember {
  mutableStateListOf<Int>().apply {
    addAll(0..10)
  }
}

Column() {
  Button(onClick = { list.shuffle() }) {
    Text("shuffle")
  }
  LazyColumn() {
    items(list, key = { it }) {
      val color = remember { Color(Random.nextInt(255), Random.nextInt(255), Random.nextInt(255)) }

      Box(
        Modifier
          .fillMaxWidth()
          .height(24.dp)
          .background(color)
          .animateItemPlacement()
      ) {
        Text(text = it.toString())
      }
    }
  }
}

3. You can use the new BringIntoView API to send a request to parents so that they scroll to bring an item into view.
-> 지정한 컴포넌트로 이동할 수 있도록 BringIntoView API 가 추가되었다.

Column(
  Modifier
    .fillMaxSize()
    .verticalScroll(rememberScrollState())) {
  val scope = rememberCoroutineScope()
  val requester = BringIntoViewRequester()

  Text(text = "Target", Modifier.bringIntoViewRequester(requester))

  (0..100).map {
    Box(
      modifier = Modifier
        .height(20.dp)
        .fillMaxWidth()
        .background(Color(Random.nextInt(255), Random.nextInt(255), Random.nextInt(255)))
    )
  }

  Button(onClick = {
    scope.launch {
      requester.bringIntoView()
    }
  }) {}

BringIntoViewRequester() 를 Modifier 를 통해서 이동하려는 컴포넌트에 지정해준 후,
bringIntoView() 함수를 호출하면 해당 위치로 스크롤 이벤트가 동작한다.

 

여기서는 Target Text 위치로 스크롤이 동작한다.

 

추가로 `bringIntoView()` 함수에 `Rect` 를 넘길 수 있는데, 대상을 기준으로 Rect 만큼 이동한 위치로 스크롤이 동작한다.

 

반응형