젯팩 컴포즈에서 제공하는 Animation API를 이용하면 사용자 인터페이스에 애니메이션 효과를 추가할 수 있다. Animation API 는 여러 클래스와 함수로 구성되며, 앱에 비교적 손쉽게 애니메이션 옵션을 적용할 수 있도록 돕는다.
✅AnimateVisibility 프로젝트 만들기
애니메이션의 가장 간단한 형태는 컴포저블이 나타나거나 사라질 때의 애니메이션일 것이다. 컴포넌트가 갑자기 나타나거나 사라지는 대신 AnimatedVisibility 컴포저블을 이용해 다양한 애니메이션 효과를 적용할 수 있다. 예를 들어, 사용자 인터페이스 요소가 홤녀에 서서히 나타나거나 화면에서 사라질 수 있다. 또는 수직, 수평 위치로 이동하면서 나타나거나 사라질 수도 있고, 확장하거나 줄어들면서 나타나거나 사라질 수도 있다. AnimatedVisibility를 호출할 때는 최소한 해당 컴포저블의 자식 컴포저블의 표시 여부를 제어하는 부울 상태 변수 파라미터를 전달해야 한다.
AnimatedVisibiliby의 기본 동작은 매우 미미해서 차이를 느끼지 못할 수도 있다. 다행히도 컴포즈 Animation API는 다양한 커스터마이즈 옵션을 제공한다. 첫 번째 옵션으로 자식 컴포저블이 나타나거나 사라질 때 적용할 수 있는 다양한 애니메이션 효과를 지정할 수 있다.
📌진입/이탈 애니메이션
AnimatedVisibility 컴포저블의 자식들이 나타나고 사라질 때 이용할 애니메이션은 enter, exit 파라미터를 이용해 선언한다.
- expandHorizontally() - 수평으로 자르는 기법을 이용해 콘텐츠를 표시한다. 애니메이션을 시작하기 전 최초에 표시할 콘텐츠의 정도를 옵션으로 제어할 수 있다.
- expandVertically() - 수직으로 자르는 기법을 이용해 콘텐츠를 표시한다. 애니메이션을 시작하기 전 최초에 표시할 콘텐츠의 정도를 옵션으로 제어할 수 있다.
- expandIn() - 수직 및 수평으로 자르는 기법을 이용해 콘텐츠를 표시한다. 애니메이션을 시작하기 전 최초에 표시할 콘텐츠의 정도를 옵션으로 제어할 수 있다.
- fadeIn() - 투명한 상태에서 불투명한 상태로 콘텐츠를 뷰에 표시한다. 초기 투명돗값은 0에서 1.0 사이의 부동소수점값으로 선언할 수 있다. 기본값은 0이다.
- fadeOut() - 반대로 사라지게 한다. 대상이 완전히 사라지기 전의 투명돗값을 0에서 1.0 사이의 부동소수점값으로 선언할 수 있다. 기본값은 0이다.
- scaleIn() - 확대한 것처럼 콘텐츠가 뷰로 확장된다. 기본적으로 콘텐츠는 아무것도 없는 상태에서 전체 크기로 확장되지만, 초기 비율값을 0에서 1.0 사이의 부동소수점값으로 지정할 수 있다.
- scaleOut() - 콘텐츠를 전체 크기에 지정한 대상 비율까지 줄인 뒤 사라지게 한다. 대상 비율의 기본값은 0이며, 0에서 1.0 사이의 부동소수점값으로 설정할 수 있다.
- shrinkHorizontally() - 콘텐츠는 수직 경계선까지 축소된 후 슬라이드하며 사라진다. 대상 폭과 슬라이드 방향을 설정할 수 있다.
- shrinkVertically() - 콘텐츠는 수평 경계선까지 축소된 후 슬라이드하며 사라진다. 대상 폭과 슬라이드 방향을 설정할 수 있다.
- shrinkOut() - 콘텐츠는 수직/수평 경계선까지 축소된 뒤 슬라이드하며 사라진다.
- slideInHorizontally() - 콘텐츠는 수평축을 따라 슬라이드하며 표시된다. 슬라이드 방향과 슬라이드를 시작할 오프셋을 지정할 수 있다.• slideIn() - 콘텐츠는 초기 오프셋값으로 지정한 각도에서 슬라이드하며 표시된다.• slideOutHorizontally(): 콘텐츠는 수평축을 따라 슬라이드하며 사라진다. 슬라이드 방향과 슬라이드를 종료할 오프셋을 지정할 수 있다.
- • slideOutVertically() - 콘텐츠는 수직축을 따라 슬라이드하며 사라진다. 슬라이드 방향과 슬라이드를 종료할 오프셋을 지정할 수 있다.
- • slideOut() - 콘텐츠는 대상 오프셋값으로 지정한 각도로 슬라이드하며 사라진다.
- • slideInVertically() - 콘텐츠는 수직축을 따라 슬라이드하며 표시된다. 슬라이드 방향과 슬라이드를 시작할 오프셋을 지정할 수 있다.
📌애니메이션 스펙과 애니메이션 이징
애니메이션 스펙은 AnimationSpec 의 인스턴스로 나타나며 이를 이용하면 애니메이션 유지 시간, 시작 지연, 스프링 튕김 효과, 반복, 애니메이션 이징을 포함한 애니메이션 동작의 여러 측면을 설정할 수 있다. Row, Column 및 다른 컨테이너 컴포저블과 마찬가지로 AnimatedVisibility 역시 고유한 스코프를 갖는다. 이 스코프 안에서 애니메이션에 대한 추가 함수에 접근할 수 있다.
tween() 함수를 이용하면 애니메이션 이징을 지정할 수 있다. 이징을 이용하면 애니메이션 속도를 증가시키거나 감소시킬 수 있다. 또한 키프레임 위치를 지정해 속도를 임의로 조정하거나 다음과 같은 FastOutSlowInEasing, LinearOutSlowInEasing, FastOutLinearEasing, LinearEasing, CubicBezierEasing
📌애니메이션 반복하기
애니메이션을 반복할 때는 RepeatableSpec 서브클래스를 이용한다. RepeatableSpec 서브클래스의 인스턴스는 repeatable() 함수를 호출해서 얻을 수 있다.
AnimatedVisibility 호출 시 진입, 이탈 애니메이션을 적용하면, 이 설정은 모든 직접/간접 자식에게 적용된다. animateEnterExit() 포디파이어를 이용하면 자식별로 개별적인 애니메이션을 지정해서 적용할 수 있다.
📌교차 페이딩 구현하기
교차 페이딩은 Crossfade 함수를 이용해 수행하며 한 컴포저블을 다른 컴포저블로 자연스럽게 대체한다. 이 함수에는 대상 상탯값을 전달하며, 이를 이용해 현재 표시된 컴포넌트를 대체할 컴포저블이 무엇이닞 결정하낟. 이후 페이딩 애니메이션 효과를 이용해 컴포저블을 대체한다.
package com.example.animatevisibility
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.material.*
import androidx.compose.ui.graphics.Color
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.ui.unit.dp
import androidx.compose.ui.Alignment
import androidx.compose.runtime.*
import androidx.compose.animation.*
import androidx.compose.animation.core.*
import com.example.animatevisibility.ui.theme.AnimateVisibilityTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AnimateVisibilityTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
MainScreen()
}
}
}
}
}
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun MainScreen() {
var boxVisible by remember { mutableStateOf(true) }
val onClick = { newState : Boolean ->
boxVisible = newState
}
Column(
Modifier.padding(20.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Crossfade(
targetState = boxVisible,
animationSpec = tween(5000)
) { visible ->
when (visible) {
true -> CustomButton(text = "Hide", targetState = false,
onClick = onClick, bgColor = Color.Red)
false -> CustomButton(text = "Show", targetState = true,
onClick = onClick, bgColor = Color.Magenta)
}
}
}
Spacer(modifier = Modifier.height(20.dp))
AnimatedVisibility(
visible = boxVisible,
enter = EnterTransition.None,
exit = ExitTransition.None
) {
Row {
Box(
Modifier
.animateEnterExit(
enter = fadeIn(animationSpec = tween(durationMillis = 5500)),
exit = fadeOut(animationSpec = tween(durationMillis = 5500))
)
.size(width = 150.dp, height = 150.dp)
.background(Color.Blue))
Spacer(modifier = Modifier.width(20.dp))
Box(
Modifier
.animateEnterExit(
enter = slideInVertically(
animationSpec = tween(durationMillis = 5500)),
exit = slideOutVertically(
animationSpec = tween(durationMillis = 5500))
)
.size(width = 150.dp, height = 150.dp)
.background(Color.Red)
)
}
}
}
}
@Composable
fun CustomButton(text: String, targetState: Boolean,
onClick: (Boolean) -> Unit, bgColor: Color = Color.Blue) {
Button(
onClick = { onClick(targetState) },
colors= ButtonDefaults.buttonColors(backgroundColor = bgColor,
contentColor = Color.White)) {
Text(text)
}
}
@Preview(showBackground = true, showSystemUi = true)
@Composable
fun DefaultPreview() {
AnimateVisibilityTheme {
MainScreen()
}
}
✅상태 주도 애니메이션
컴포즈의 Animation API를 이용하면 상태 변화에 따라 애니메이션 효과를 이용할 수 있다. 상태 변화에 따라 레이아웃에 놓인 컴포넌트의 형태, 위치, 방향, 크기가 바뀌는 경우 하나 이상의 애니메이션을 상태 함수로서 이용함으로써 시각적인 전환을 애니메이션으로 나타낼 수 있다. 상태 함수로서의 애니메이션은 animate*AsState 함수라고 불린다. 이 함수들은 모두 동일한 이름 규칙을 이용한다. 와이드카드 문자는 해당 애니메이션을 트리거하는 상태 유형으로 대체된다. 컴포즈는 Bounds, Color, Dp, Float, Int, IntOffset, IntSize, Offset, Rect, Size 데이터 타입에 대한 상태 애니메이션 함수를 제공하며, 이를 이용해 대부분의 애니메이션 요구사항을 처리할 수 있다. 이 함수들은 변경 결과를 하나의 상탯값으로 애니메이션한다.
📌AnimateState 프로젝트 만들기
다른 개념은 앞선 내용과 비슷한데, spring같은 동작은 애니메이션에 튕김 효과를 추가하며 spring() 함수에 animationSpec 파라미터를 전달해서 적용할 수 있다. 스프링 효과를 이해할 때는 애니메이션의 시각점에 스프링의 한쪽 끝이 연결되어 있고 다른 한쪽 끝은 박스에 연결되어 있는 모습을 떠올리면 도움이 된다. spring()은 댐핑 비율과 강도를 파라미터로 받는데, 댐핑 비율은 튕김 효과가 감소하는 속도를 정의하며 부동소수점값으로 선언한다. 1.0은 튕김이 없는, 0.1은 가장 많이 튕기는 상태. DampingRatioHighBouncy, DampingRatioLowBouncy, DampingRatioMediumBouncy, DampingRatioNoBouncy와 같이 미리 정의된 상수를 이용할 수 있다. 강도 파라미터는 스프링의 세기를 정의한다. 강도가 낮을수록 튕김 효과에 의한 움직임의 범위가 커진다. StiffnessHigh, Low, Medium, MediumLow, VeryLow 처럼 스프링 강도를 조정할 수 있다.
📌키프레임 다루기
키프레임을 이용하면 애니메이션 타임라인의 특정한 지점에 다양한 유지 시간이나 이징값을 적용할 수 있다. 키프레임은 animationSpec 파라미터를 통해 애니메이션에 적용되며, keyframes() 함수를 이용해 지정한다. keyframes() 함수는 키프레임 데이터를 포함한 람다를 전달받아 keyframeSpec 인스턴스를 반환한다.
📌여러 애니메이션 조합하기
updateTransition() 함수를 이용하면 하나의 대상 상태를 기반으로 여러 애니메이션을 병렬로 실행할 수 있다. 이 함수에 대상 상태를 전달하면 transition 인스턴스를 반환하며, 이 인스턴스에는 여러 자식 애니메이션을 추가할 수 있다. 대상 상태가 변경되며 이 트랜지션은 모든 자식 애니메이션을 동시에 실행한다.
Animation Inspector는 안드로이드 스튜디오에 내장되어 있는 도구로, 이를 이용하면 애니메이션 타임라인에 직접 접근할 수 있고 수동으로 애니메이션 시퀀스를 앞뒤로 스크롤할 수 있다. (트랜지션 기반 애니메이션을 제공해야만 이용 가능.)
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AnimateStateTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
RotationDemo()
}
}
}
}
}
enum class BoxColor {
Red, Magenta
}
@Composable
fun RotationDemo() {
var rotated by remember { mutableStateOf(false) }
val angle by animateFloatAsState(
targetValue = if (rotated) 360f else 0f,
animationSpec = tween(durationMillis = 2500, easing = LinearEasing)
)
Column(horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()) {
Image(
painter = painterResource(R.drawable.propeller),
contentDescription = "fan",
modifier = Modifier
.rotate(angle)
.padding(10.dp)
.size(300.dp)
)
Button(
onClick = { rotated = !rotated },
modifier = Modifier.padding(10.dp)
) {
Text(text = "Rotate Propeller")
}
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
AnimateStateTheme {
RotationDemo()
}
}
@Composable
fun ColorChangeDemo() {
var colorState by remember { mutableStateOf(BoxColor.Red) }
val animatedColor: Color by animateColorAsState(
targetValue = when (colorState) {
BoxColor.Red -> Color.Magenta
BoxColor.Magenta -> Color.Red
},
animationSpec = tween(4500)
)
Column(horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()) {
Box(
modifier = Modifier
.padding(20.dp)
.size(200.dp)
.background(animatedColor)
)
Button(
onClick = {
colorState = when (colorState) {
BoxColor.Red -> BoxColor.Magenta
BoxColor.Magenta -> BoxColor.Red
}
},
modifier = Modifier.padding(10.dp)
) {
Text(text = "Change Color")
}
}
}
@Preview(showBackground = true)
@Composable
fun ColorChangePreview() {
AnimateStateTheme {
ColorChangeDemo()
}
}
enum class BoxPosition {
Start, End
}
@Composable
fun MotionDemo() {
val screenWidth = (LocalConfiguration.current.screenWidthDp.dp)
var boxState by remember { mutableStateOf(BoxPosition.Start)}
val boxSideLength = 70.dp
val animatedOffset: Dp by animateDpAsState(
targetValue = when (boxState) {
BoxPosition.Start -> 0.dp
BoxPosition.End -> screenWidth - boxSideLength
},
spring(dampingRatio = DampingRatioHighBouncy, stiffness = StiffnessVeryLow)
)
Column(modifier = Modifier.fillMaxWidth()) {
Box(
modifier = Modifier
.offset(x = animatedOffset, y = 20.dp)
.size(boxSideLength)
.background(Color.Red)
)
Spacer(modifier = Modifier.height(50.dp))
Button(
onClick = {
boxState = when (boxState) {
BoxPosition.Start -> BoxPosition.End
BoxPosition.End -> BoxPosition.Start
}
},
modifier = Modifier.padding(20.dp)
.align(Alignment.CenterHorizontally)
) {
Text(text = "Move Box")
}
}
}
@Preview(showBackground = true)
@Composable
fun MotionDemoPreview() {
AnimateStateTheme {
MotionDemo()
}
}
@Composable
fun TransitionDemo() {
var boxState by remember { mutableStateOf(BoxPosition.Start)}
var screenWidth = LocalConfiguration.current.screenWidthDp.dp
val transition = updateTransition(targetState = boxState,
label = "Color and Motion")
val animatedColor: Color by transition.animateColor(
transitionSpec = {
tween(4000)
}
) { state ->
when (state) {
BoxPosition.Start -> Color.Red
BoxPosition.End -> Color.Magenta
}
}
val animatedOffset: Dp by transition.animateDp(
transitionSpec = {
tween(4000)
}
) { state ->
when (state) {
BoxPosition.Start -> 0.dp
BoxPosition.End -> screenWidth - 70.dp
}
}
Column(modifier = Modifier.fillMaxWidth()) {
Box(
modifier = Modifier
.offset(x = animatedOffset, y = 20.dp)
.size(70.dp)
.background(animatedColor)
)
Spacer(modifier = Modifier.height(50.dp))
Button(
onClick = {
boxState = when (boxState) {
BoxPosition.Start -> BoxPosition.End
BoxPosition.End -> BoxPosition.Start
}
},
modifier = Modifier.padding(20.dp)
.align(Alignment.CenterHorizontally)
) {
Text(text = "Start Animation")
}
}
}
@Preview(showBackground = true)
@Composable
fun TransitionDemoPreview() {
AnimateStateTheme {
TransitionDemo()
}
}
✅Canvas 그래픽 그리기
Canvas 컴포넌트는 2D 그래픽을 그릴 수 있는 표면을 제공한다. 하지막 내부적으로 Canvas는 단지 그래픽을 그리는 영역 이상의 것을 제공한다. 그래픽한 콘텐츠의 상태를 자동으로 유지하고 관리해준다. 그 자체의 스코프도 가지고 있어서 이를 이용하면 크기 정보와 현재 영역의 중앙점을 포함한 캔버스 영역 프로퍼티에 접근할 수 있으며 일련의 함수를 이용해 도형, 선, 경로를 그리거나 삽입을 정의하거나 회전을 수행하는 등의 작업도 할 수 있다.
📌CanvasDemo 프로젝트 만들기
- 대각선을 그리기 위해서는 DrawScope가 제공하는 크기 프로퍼티에 접근해서 캔버스의 크기 정보를 얻어야 한다. drawLine() API 함수는 시작점과 끝점의 x, y 좌표를 알아야 한다.
- drawLine() 함수에는 선의 굵기와 색상도 전달해야 한다.
- Canvas 위에서 수행되는 모든 형태의 선 그리기는 PathEffect 인스턴스를 설정하고 그리기 함수를 호출할 때 pathEffect 인수로 전달해 점선으로 나타낼 수 있다.
- 또한, drawRect() 함수를 이용하면 사각형을 그릴 수도 있고, 위치 조정도 가능하다. inset() 함수를 이용해 Canvas 컴포넌트의 경계를 수정할 수도 있다.
- drawRoundRect() 함수를 이용하면 둥근 모서리, style 프로퍼티에 Stroke 지정해 테투리만 가진 사각형으로 그릴 수 있다. Canvas 컴포넌트 위에 그려진 요소들은 해당 스코프의 rotate() 함수를 호출해서 회전시킬 수 있다.
- drawCircle() 함수를 이용해 원을 그릴 수 있다. DrawScope의 center 프로퍼티를 이용해 캔버스의 중심을 얻을 수 있다. 여기에 타원을 그리고 싶다면 drawOval() 함수를 이용하면 된다.
- Brush 컴포넌트를 이용하면 그레이디언트 패턴(수평, 수직, 선형, 원형, 스위핑 그레이트)을 이용해 도형 내부를 채울 수 있다. 그레이디언트는 그림자 효과를 추가할 때 매우 유용하다.
- DrawScope의 drawArc() 함수를 이용하면 지정한 사각형 안에 부채꼴을 그릴 수 있다. 함수를 호출할 때는 Brush 또는 Color 설정과 함께 시작 각도 및 내각을 전달해야 한다.
- 경로도 그릴 수 있다. 일렬의 좌표들을 연결하는 선을 그린 것이기에, 경로는 Path 클래스 인스턴스에 저장되며, 정의된 경로를 drawPath() 함수에 전달하면 경로가 그려진다. 디자인 할 때는 moveTo() 호출해 시작 지점 정의, 이후 lineTo() 또는 relativeLineTo() 함수를 이용해 다음 위치로 선을 연결한다.
- drawPoints() 함수를 이용하면 Offset 인스턴스 리스트로 지정한 위치마다 점을 찍을 수 있다. pointMode 파라미터를 이용하면 각 점을 개별적으로 찍을 것인지 또는 Lines/Polygon 모드를 이용해 선으로 연결할 것인지 제어할 수 있다.
- drawImage() 함수를 이용하면 이미지 리소스를 캔버스 위에 그릴 수 있다. 리소스를 프로젝트에 추가 후 이미지 파일을 다운로드한 샘플 코드의 images 폴더에서 찾는다. 샘플 코드는 url에서 다운로드. 또 이 함수를 이용해서 색상 필터를 렌더링된 이미지에 적용할 수 있다.
@Composable
fun MainScreen() {
DrawImage()
}
@Composable
fun DrawImage() {
val image = ImageBitmap.imageResource(id = R.drawable.vacation)
Canvas(
modifier = Modifier
.size(360.dp, 270.dp)
) {
drawImage(
image = image,
topLeft = Offset(x = 0f, y = 0f),
colorFilter = ColorFilter.tint(
color = Color(0xADFFAA2E),
blendMode = BlendMode.ColorBurn
)
)
}
}
@Composable
fun DrawPoints() {
Canvas(modifier = Modifier.size(300.dp)) {
val height = size.height
val width = size.width
val points = mutableListOf<Offset>()
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
)
}
}
@Composable
fun DrawPath() {
Canvas(modifier = Modifier.size(300.dp)) {
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()
}
drawPath(
path = path,
Color.Blue,
)
}
}
@Composable
fun DrawArc() {
Canvas(modifier = Modifier.size(300.dp)) {
drawArc(
Color.Blue,
startAngle = 20f,
sweepAngle = 90f,
useCenter = true,
size = Size(250.dp.toPx(), 250.dp.toPx())
)
}
}
@Composable
fun ShadowCircle() {
Canvas(modifier = Modifier.size(300.dp)) {
val radius = 150.dp.toPx()
val colorList: List<Color> =
listOf(Color.Blue, Color.Black)
val brush = Brush.horizontalGradient(
colors = colorList,
startX = 0f,
endX = 300.dp.toPx(),
tileMode = TileMode.Repeated
)
drawCircle(
brush = brush,
radius = radius
)
}
}
@Composable
fun RadialFill() {
Canvas(modifier = Modifier.size(300.dp)) {
val canvasWidth = size.width
val canvasHeight = size.height
val radius = 150.dp.toPx()
val colorList: List<Color> = listOf(Color.Red, Color.Blue,
Color.Magenta, Color.Yellow, Color.Green, Color.Cyan)
val brush = Brush.radialGradient(
colors = colorList,
center = center,
radius = radius,
tileMode = TileMode.Repeated
)
drawCircle(
brush = brush,
center = center,
radius = radius
)
}
}
@Composable
fun GradientFill() {
Canvas(modifier = Modifier.size(300.dp)) {
val canvasSize = size
val colorList: List<Color> = listOf(Color.Red, Color.Blue,
Color.Magenta, Color.Yellow, Color.Green, Color.Cyan)
val brush = Brush.horizontalGradient(
colors = colorList,
startX = 0f,
endX = 300.dp.toPx(),
tileMode = TileMode.Repeated
)
drawRect(
brush = brush,
size = canvasSize
)
}
}
@Composable
fun DrawOval() {
Canvas(modifier = Modifier.size(300.dp)) {
val canvasWidth = size.width
val canvasHeight = size.height
drawOval(
color = Color.Blue,
topLeft = Offset(x = 25.dp.toPx(), y = 90.dp.toPx()),
size = Size(
width = canvasHeight - 50.dp.toPx(),
height = canvasHeight / 2 - 50.dp.toPx()
),
style = Stroke(width = 12.dp.toPx())
)
}
}
@Composable
fun DrawCircle() {
Canvas(modifier = Modifier.size(300.dp)) {
val canvasWidth = size.width
val canvasHeight = size.height
drawCircle(
color = Color.Blue,
center = center,
radius = 120.dp.toPx()
)
}
}
@Composable
fun DrawRect() {
Canvas(modifier = Modifier.size(300.dp)) {
rotate(45f) {
drawRect(
color = Color.Blue,
topLeft = Offset(200f, 200f),
size = size / 2f
)
}
}
}
@Composable
fun DrawLine() {
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 = PathEffect.dashPathEffect(
floatArrayOf(30f, 10f, 10f, 10f), phase = 0f)
)
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
CanvasDemoTheme {
MainScreen()
}
}