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 튜토리얼

반응형