Android
핵심만 골라 배우는 젯팩 컴포즈 Study 4주차
가짜 개발자
2023. 2. 8. 19:37
Chapter 37 ~ 41
Chapter 37
AnimateVisibility
- 진입, 이탈 애니메이션 정의
- expandHorizontally()
- expandVertically()
- expandIn()
- fadeIn()
- fadeOut()
- scaleIn()
- scaleOut()
- shrinkHorizontally()
- …
- 애니메이션 조합도 가능
AnimatedVisibility(
visible = boxVisible,
enter = fadeIn() // + expandHorizontally(),
exit = slideOutVertically()
) {
...
}
- AnimationSpec : 애니메이션 유지 시간, 시작 지연, 스프링, 튕김 효과, 반복, 에니메이션 이징(애니메이션 속도 증가 또는 감소) 등 애니메이션 동작 설정 가능.
- 애니메이션 유지 시간을 제어하려면 tween() 함수를 호출해 DurationBasedAnimationSpec 인스턴스를 생성하고 이를 애니메이션 효과 함수 호출 시 파라미터로 전달해야 함.
enter = fadeIn(animationSpec = tween(durationMillis = 5000))
- RepeatableSpec : 애니메이션 반복할 때는 RepeatableSpec 서브클래스 이용.
- repeatable() 함수를 호출해서 얻을 수 있음.
- 이 함수는 반복할 애니메이션과 RepeatMode 파라미터를 받음.
- 이 파라미터는 애니메이션 순서를 **시작 → 끝(RepeatMode.Restart)**으로 적용할지, **끝 → 시작(RepeatMode.Reverse)**으로 적용할지를 지정.
enter = fadeIn(
animationSpec = repeatable(
10,
animation = tween(durationMillis = 2000),
repeatMode = RepeatMode.Reverse
)
)
- AnimatedVisibility 호출 시 진입, 이탈 애니메이션 적용하면 모든 직접, 간접 자식에게 적용됨.
- animateEnterExit() 모디파이어를 이용하면 자식별로 개별적인 애니메이션을 지정해서 적용 가능.
- 모디파이어의 애니메이션만 이용하고 싶다면 부모인 AnimatedVisibility 인스턴스에서 EnterTransition.None, ExitTransition.None 지정.
animateEnterExit( enter = slideInVertically(animationSpec = tween(durationMillis = 5500)), exit = slideOutVertically(animationSpec = tween(durationMillis = 5500)) )
- 애니메이션 자동 시작
- MutableTransitionState 인스턴스를 전달하여 수행.
- 특별한 목적을 가진 상태로 currentState, targetState 프로퍼티 포함.
- 기본적으로 현재 상태, 대상 상태는 동일하게 설정되어 있음.
- MutableTransitionState 인스턴스를 생성할 때 초기 상태가 설정.
- 트래지션 상태 인스턴스가 생성될 때 targetState 프로퍼티를 true로 설정.
val state = remember { MutableTransitionState(false)}
state.apply { targetState = true }
- 교차 페이딩 구현
- Crossfade 함수 이용.
- 이 함수에는 targetState를 전달, 이를 이용해 현재 표시된 컴포넌트를 대체할 컴포저블을 결정.
Crossfade(targetState = boxVisible, animationSpec = tween(5000)) { visible ->
Chapter 38
상태 주도 애니메이션
- animate*AsState 함수라 불림.
- 와일드카드 문자(*)로 해당 애니메이션을 트리거하는 상태 유형으로 대체.
- 이 함수들은 변경 결과를 하나의 상탯값으로 애니메이션 함.
- targetValue를 지정하고, 현재 상탯값에서 대상 상탯값으로 변경을 애니메이션으로 표시.
- animateColorAsState, animateFloatAsState, animateDpAsState..
- 스프링 효과
- damping ratio, stiffness를 파라미터로 받음.
- 댐핑 비율은 튕김 효과가 감소하는 속도를 정의하며 부동 소수점 값으로 선언. (1.0은 튕김 없는 상태, 0.1은 가장 많이 튕기는 상태)
- DampingRatioHighBouncy, StiffnessHigh 같은 상수 이용 가능.
- 강도는 스프링의 세기를 정의. 강도가 낮을수록 튕김 효과에 의한 움직임의 범위가 커짐.
animationSpec = spring(dampingRatio = DampingRatioHighBouncy, stiffness = StiffnessVeryLow)
- 키 프레임
- 애니메이션 타임라인의 특정한 지점에 다양한 유지 시간이나 이징값 적용 가능.
- animationSpec 파라미터를 통해 애니메이션에 적용.
- keyframes() 함수를 이용해 지정.
- keyframes() 함수는 키 프레임 데이터를 포함한 람다를 전달받아 KeyframeSpec 인스턴스를 반환.
- 애니메이션을 완료하는 데 필요한 전체 유지 시간을 선언하는 것으로 시작. 이후 전체 시간에 타임스탬프를 찍음.
animationSpec = keyframes {
durationMillis = 1000
100.dp.at(10)
110.dp.at(500)
200.dp.at(700)
}
- updateTransition()
- 하나의 대상 상태를 기반으로 여러 애니메이션을 병렬로 실행 가능.
- targetState를 전달하면 Transition 인스턴스를 반환.
- targetState가 변경되며 이 트랜지션은 모든 자식 애니메이션을 동시에 실행.
- label로 트랜지션 식별 가능.
- Transition 클래스는 자식에 애니메이션을 추가하기 위해 이용되는 함수의 컬렉션을 포함.
- 애니메이션의 단위 타임에 따라 animate<Type>() 이라는 이름 규칙을 이용.
val transition = updateTransition(targetState = myState, label = "hi") val myAnimation: <Type> by transion.animate<Type>( transitionSpec = { // 애니메이션 스펙(tween, spring..) } ) { // 현재 상태를 기반으로 새로운 대상 상태를 식별할 코드 }
Chapter 39
Canvas
- 선 그리기와 캔버스 크기 얻기
- drawLine()
- 선의 시작점과 끝 점의 x, y 좌표를 알아야 함.
- 선의 굵기와 색상도 전달해야 함.
Canvas(modifier = Modifier.size(300.dp)) { val height = size.height val width = size.width drawLine( start = Offset(x = 0f, y = 0f), end = Offset(x = width, y = height), color = Color.Blue, strokeWidth = 16.0f ) }
- 점선 그리기
- 점선을 그릴 때는 PathEffect 인스턴스의 dashPathEffect() 호출.
- 여기에 부동 소수점 수 배열 전달.
- 부동소수점 수는 선을 그리는 구간, 그리지 않는 구간을 픽셀 단위로 나타낸 것.
- 이 배열에는 값이 최소 2개 이상 있어야 함. 구간값의 수는 짝수여야 함.
pathEffect = PathEffect.dashPathEffect(floatArrayOf(30f, 10f, 10f, 10f), phase = 0f)
- 사각형 그리기
- drawRect()
- 모서리 좌표 지정 가능.
- inset()으로 캔버스의 가장자리에 다양한 설정 가능.
- drawRoundRect()로 둥근 모서리를 가진 사각형 그릴 수 있음. cornerRadius 컴포넌트 전달해야 함.
drawRect(color = Color.Blue, topLeft = Offset(x=350f, y=300f), size = size) drawRoundRect( color = Color.Blue, size = size, topLeft = Offset(20f, 20f), style = Stroke(width = 8.dp.toPx()), cornerRadius = CornerRadius(x = 30.dp.toPx(), y = 30.dp.toPx()) )
- 회전 시키기
- rotate()
rotate(45f) { drawRect(color = Color.Blue, topLeft = Offset(200f, 200f), size = size / 2f) }
- 원과 타원 그리기
- 원 그리기 - drawCircle()
- 타원 그리기 - drawOval()
drawCircle(color = Color.Blue, center = center, radisu = 120.dp.toPx()) drawOval(color = Color.Blue, topLeft = Offset(x = 25.dp.toPx(), y = 90.dp.toPx())
- 그레디언트 그리기
- Brush
val brush = Brush.horizotalGradient(colors = colorList, startX = 0f, endX = 300.dp.toPx(), tileMode = TileMode.Repeated)
- 부채꼴 그리기
- drawArc()
- Brush 또는 Color 설정과 함께 시작 각도 및 내각을 전달해야 함.
drawArc(Color.Blue, startAngle = 20f, sweepAngle = 90f, useCenter = true, size = Size(250.dp.toPx(), 250.dp.toPx())
- 경로 그리기
- 경로는 본질적으로 캔버스 영역 안의 일련의 좌표들을 연결하는 선을 그린 것.
- Path 클래스 인스턴스에 저장됨.
- 정의된 경로를 drawPath()에 전달하면 Canvas 위에 경로가 그려짐.
- moveTo() 함수를 호출해 첫 번째 선의 시작 지점을 정의.
- 이후 lineTo() 또는 relativeLineTo() 호출해 다음 위치로 선을 연결.
- lineTo()는 다음 x, y 좌표를 받아 부모 Canvas의 왼쪽 위 모서리를 기준으로 한 상대 좌표값.
- relativeLineTo()는 이전 위치를 기준으로 하는 좌표를 받음.
- close()를 호출해 그리기를 완료해야 함.
val path = Path().apply { moveTo(0f, 0f) quadraticBezierTo( 50.dp.toPx(), 200.dp.toPx(), 300.dp.toPx(), 300.dp.toPx() ) lineTo(270.dp.toPx(), 100.dp.toPx()) quadraticBezierTo(60.dp.toPx(), 80.dp.toPx(), 0f, 0f) close() }
- 점 그리기
- drawPoints()를 이용해 Offset 인스턴스 리스트로 지정한 위치마다 점을 찍을 수 있음.
- pointMode 파라미터를 이용해 각 점을 개별적으로 또는 Lines / Polygon 모드를 이용해 선으로 연결할 것인지 제어 가능.
for (x in 0..size.width.toInt()) { val y = (sin(x * (2f * PI / width)) * (height / 2) + (height / 2)).toFloat() points.add(Offset(x.toFloat(), y)) } drawPoints( points = points, strokeWidth = 3f, pointMode = PointMode.Points, color = Color.Blue )
- 이미지 그리기
- drawImage()로 이미지 리소스를 캔버스 위에 그를 수 있음.
- 캔버스 영역의 왼쪽 위 모서리로부터 이미지 위치를 설정한 Offset 인스턴스와 함께 drawImage() 함수에 전달.
drawImage(iamge = image, topLeft = Offset(x = 0f, y = 0f))
Chapter 40
ViewModel
- 구성 변경에서 remeberSaveable로 데이터를 저장할 수 있지만, 컴포저블 내부에 로직을 유지해야 함.
- 앱이 성장함에 따라 데이터와 로직을 컴포저블과 분리해야 함.
- ViewModel 안에서 데이터를 관찰 하는 방법
- 컴포즈의 상태 메커니즘 이용.
class MyViewModel : ViewModel() { var customerCount by mutableStateOf(0) fun increaseCount() { customerCount++ } } @Composable fun TopLevel(model: MyViewModel = MyViewModel()) { MainScreen(model.customerCount) { model.increaseCount() } } @Composable fun MainScreen(count: Int, addCount: () -> Unit = {}) { Column(horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth()) { Text(text = "Total customers = $count", Modifier.padding(10.dp)) Button(onClick = addCount) { Text(text = "Add a Customer") } } }
- 젯팩 LiveData 컴포넌트 이용.
- **observeAsState()**는 LiveData<T>를 관찰. 변경될 때마다 State<T> 객체 반환.
- **observaAsState()**는 컴포지션에 있는 동안에만 라이브데이터 관찰.
@Composable fun TopLevel(model: MyViewModel = MyViewModel()) { var customerName : String by model.customerName.observeAsState("") }
- ViewModel은 컴포저블 안에서 이용해야만 쓸모가 있음.
- ViewModel 인스턴스를 컴포저블에 파라미터로 전달해, 컴포저블에서 상탯값과 함수에 접근할 수 있도록 해야함.
- 컴포저블 계층 맨 위에 위치한 컴포저블에서 수행할 것을 권장.
Chapter 41
ViewModel 튜토리얼
반응형