티스토리 뷰

안드로이드/코드

Compose MotionLayout

알렌보이스 2022. 11. 2. 20:19
728x90

MotionLayout은 이전 포스팅에서 다루었던 ConstraintLayout을 추가하였다면 별도로 설치하실 필요는 없습니다.

1) 기본 사용 방법

@OptIn(ExperimentalMotionApi::class)
@Composable
fun MotionLayoutTest() {
    var state by remember {
        mutableStateOf(false)
    }
    val progress by animateFloatAsState(
        targetValue = if (state) 1f else 0f,
        animationSpec = tween(500)
    )

    MotionLayout(
        start = startConstraintSet(),
        end = endConstraintSet(),
        progress = progress
    ) {

        Box(
            modifier = Modifier
                .layoutId("box")
                .size(50.dp)
                .background(Color(0xFFFF9E80))
        )

        Button(
            onClick = { state = !state },
            modifier = Modifier.layoutId("button")
        ) {
            Text(text = if (state) "초기화" else "시작")
        }
    }
}

private fun startConstraintSet(): ConstraintSet = ConstraintSet(
    """
        {
            box: {
                top: ['parent', 'top', 30],
                bottom: ['parent', 'bottom'],
                start: ['parent' , 'start', 20],
            },
            button: {
                width: "parent", 
                start: ['parent', 'start', 20],
                end: ['parent', 'end', 20],
                bottom: ['parent', 'bottom', 30]
            }
        }
    """
)

private fun endConstraintSet(): ConstraintSet = ConstraintSet(
    """
        {
            box: {
                top: ['parent', 'top', 30],
                bottom: ['parent', 'bottom'],
                end: ['parent' , 'end', 20],
            },
            button: {
                width: "parent",
                start: ['parent', 'start', 20],
                end: ['parent', 'end', 20],
                bottom: ['parent', 'bottom', 30]
            }
        }
    """
)

MotionLayout()에 start와 end를 ConstraintSet을 통해 값을 지정하면 모션을 사용할 수 있습니다.

이전 ConstraintLayout 포스팅에서 사용했던 것 처럼 사용해도 작동은 하나 몇 속성을 제외하고는 적용하는 방법을 찾지 못했습니다.

ConstraintSet을 중괄호가 아닌 소괄호로 하면 JSON형식으로 넣을 수 있습니다.

box: {
    top: ['parent', 'top', 30],
    bottom: ['parent', 'bottom'],
    start: ['parent' , 'start', 20],
}

이 코드로 예시를 들면

Modifier.layoutId가 box인 Composable의 top이 부모의 탑에서 30Dp만큼 떨어지게 만들어 줍니다. (bottom과 start 동일하게 속성에 맞게 적용)

var state by remember {
    mutableStateOf(false)
}
val progress by animateFloatAsState(
    targetValue = if (state) 1f else 0f,
    animationSpec = tween(500)
)

MotionLayout(
    start = startConstraintSet(),
    end = endConstraintSet(),
    progress = progress
)

MotionLayout은 시작과 끝을 따로 정의하여 progress의 진행에 따라 모션이 동작하게 됩니다.

progress는 0~1의 값의 사용이 기본입니다.

해당 예제에서는 버튼을 클릭하여 state의 값에 변화를 주어 모션을 동작하게 하였습니다.

Screen_Recording_20221031_180136_ComposeStudy_1.gif

2) MotionSence

MotionLayout(
    motionScene = myMotionScene(),
    progress = progress
)

@Composable
private fun myMotionScene() = MotionScene(
    """
    {
      ConstraintSets: {
        start: {
            box: {
                top: ['parent', 'top', 30],
                bottom: ['parent', 'bottom'],
                start: ['parent' , 'start', 20],
                custom: {
                  background: "#84FFFF"
                }
            },
            button: {
                width: "parent", 
                start: ['parent', 'start', 20],
                end: ['parent', 'end', 20],
                bottom: ['parent', 'bottom', 30]
            }
        },
        end: {
          box: {
                top: ['parent', 'top', 30],
                bottom: ['parent', 'bottom'],
                end: ['parent' , 'end', 20],
                custom: {
                  background: "#FF9E80"
                }
            },
            button: {
                width: "parent",
                start: ['parent', 'start', 20],
                end: ['parent', 'end', 20],
                bottom: ['parent', 'bottom', 30]
            }
        }
      }
    }
    """

MotionScene는 위에서 사용한 start와 end가 결합된 형태입니다.

해당 부분의 코드가 “””에 감싸져 있고 들였기 등의 불편한 점이 있어서 파일로 따로 빼서 사용도 가능합니다.

raw에 my_motion이라는 json5파일을 생성해 줍니다.

그리고 위에 작성한 코드를 저장해 줍니다.

val context = LocalContext.current
val motionSceneContent = remember {
    context.resources
        .openRawResource(R.raw.my_motion)
        .readBytes()
        .decodeToString()
}

MotionLayout(
    motionScene = MotionScene(motionSceneContent),
    progress = progress
)

그 후 위의 방법을 이용해서 모션 적용을 하실 수 있습니다.

3) 색상 변경

색상의 변경의 경우 기본 속성에서는 제공하지 않습니다.

이러한 경우에는 custom을 이용하면 됩니다.

box: {
  top: ['parent', 'top', 30],
  bottom: ['parent', 'bottom'],
  start: ['parent' , 'start', 20],
  custom: {
    background: "#84FFFF"
  }
}

코드가 길어져서 일부만 가져왔습니다. start와 end의 색상은 다르게 설정하였습니다.

custom에 원하는 속성의 키값을 지정합니다.

그다음 레이아웃 영역에서는 다음과 같이 설정합니다.

val boxProperties = motionProperties(id = "box")

Box(
    modifier = Modifier
        .layoutId("box")
        .size(50.dp)
        .background(boxProperties.value.color("background"))
)

정의한 아이디에 해당하는 속성들을 motionProperties를 통해 가져올 수 있고 위에서 지정한 키값을 통해 원하는 값을 가져올 수 있습니다.

가져올 수 있는 단위로는 Color, Dp, int, float, String, textUnit이 있습니다.

Screen_Recording_20221031_191641_ComposeStudy_1.gif

4) Transitions

{
    ConstraintSets: {
        ...
    }
    Transitions: {
      default: {
        from: 'start',
        to: 'end',
        KeyFrames: {
          KeyAttributes: [
            {
              target: ['box'],
              frames: [0, 50, 100],
              translationY: [0, -200, 0]
            }
          ]
        }
      }
    },
}

Transitions은 위에서 만든 동작에서 전환 효과를 추가합니다.

위의 예시에서는 frames은 0~100 범위 안으로 프레임으로 나눈 뒤

start에서 end로 애니메이션이 진행될 때 중간에 설정한 프레임(50)에 설정한 동작(translationY>-200)의 전환 효과를 추가하는 작업입니다.

Screen_Recording_20221031_191641_ComposeStudy_1.gif

728x90

'안드로이드 > 코드' 카테고리의 다른 글

Compose QR코드 스캐너  (2) 2022.11.04
Android Compose MotionLayout 2  (0) 2022.11.03
Compose ConstraintLayout  (0) 2022.11.02
Compose Naver Map  (1) 2022.10.29
Compose GoogleMap  (0) 2022.10.27
댓글