가끔 안드로이드에서 UI를 짤 때
앱의 로고와 같이 특정 ui가 메인 폰트와 다른 폰트를 사용한다거나,
png 파일을 사용하기에는 깨질 것 같아 불안한 상황이 있다.
나는 그런 상황일 때 svg 파일로 import하여 Image Vector로 추출하여 해결 했었는데,
Svg 파일을 Compose에서 사용할 Image Vector로 변환하는 방법에 대해 알아보자.
디자이너와 협업하고 있는 개발자라면 많은 파일들이 img 또는 svg로 가득 쌓여있을 것이다.
PNG는 Portable Network Graphics 로 이미지를 무손실 압축한 형식이기 때문에 고화질로 업로드 해야할 비교적 복잡한 이미지 파일을 사용할 때 적용하는 것이 좋다. 그라데이션과 같이 여러 색상이 덮혀져 있는 이미지 같은 경우 SVG를 이용하면 씹히는 경우가 종종 있다
반면에 SVG는 Scalable Vector Graphics 로 XML 기반 이미지 형식이다. Svg 파일을 Image Vector로 변환했을때 해당 파일이 Xml임을 지나가다 한번 쯤은 다들 봤을 것이다.
또, PNG와는 다르게 애니메이션 효과를 지원한다는 장점이 있어 확대해도 깨지지 않고, 애니메이션 효과가 필요한 UI(Ex. 로고) 에 적용하는 것이 좋다.
이외에도 JSON 파일 형식의 Lottie도 있으나, 다음 기회에 적어보도록 하겠다.
나는 피그마를 사용하기에 피그마를 사용한다는 기준에 맞춰 작성하도록 하겠다.
원하는 UI를 경계에 맞춰 선택한 다음, 왼편 하단을 보면 Export 메뉴가 있을 것이다.
해당 파일을 Svg로 추출할 것이기 때문에, Svg옵션을 선택하여 Export 하면 된다.
(1x, 1.25x 와 같이 확대 옵션은 Png와 JPEG만 지원한다)
Svg 파일은 Xml 기반으로 되어 있으나, 날 것 그대로의 Svg 파일을 이용할 수는 없다.
View-Based 기반 UI를 짤 때도 Svg 파일을 Image Vector로 변환했던 과정을 거친 기억이 한번 쯤은 있을 것이다.
위의 사이트는 Svg 파일을 Image Vector로 변환하는 과정을 코드로 적어주는 사이트인데,
Val 선언자를 선택할 것인지, function을 선택할 것인지에 대한 옵션이 있다.
우선, Input 파일은 설정 Icon에 사용될 Trailing icon이다.
Convert As val로 적용할 경우 Output은 ImageVector형의 불변변수이다.
반면에 Convert as Function은 ImageVector을 리턴하는 함수이다.
var _vector: ImageVector? = null
public val Vector: ImageVector
get() {
if (_vector != null) {
return _vector!!
}
_vector = ImageVector.Builder(
Image Vector의 정보 (너비, 길이)
).apply {
group {
path(
Image Vector의 경로
) {
Image Vector의 위치
}
}
}.build()
return _vector!!
}
}
//DrawLogo 함수
@Composable
fun DrawLogo(modifier: Modifier = Modifier) {
Image(
imageVector = vector,
contentDescription = null,
modifier = modifier,
contentScale = ContentScale.FillBounds,
alignment = Alignment.Center
)
}
자주 쓰이는 아이콘 같은 경우 Kotlin Object 안에 넣은 다음,
해당 이미지 벡터를 그리는 함수를 선언하면 아이콘과 화면의 클래스가 분리되어 관리하기 용이하다는 장점이 있다. 또한 Object로 분리하여 함수를 이용해 ImageVector을 사용 시,
LoginLogoIv.DrawLogo(
modifier = Modifier
.height(76.dp)
.width(257.26.dp))
간단하게 modifier의 크기만 선언해서 코드의 가독성을 높일 수 있다.
@Composable
fun rememberVector(): ImageVector {
return remember {
ImageVector.Builder(
Image Vector의 정보 (너비, 길이)
).apply {
group {
path(
Image Vector의 경로
) {
Image Vector의 위치
}
}
}.build()
}
}
해당 화면에서만 사용하거나, 굳이 분리해서 관리할 필요가 없다 판단이 들 경우에는 사용할 화면의 클래스에 Function으로 정의하여 ImageVector이 들어갈 수 있는 변수에 할당해주면 된다.
사용 예시는 다음과 같다.
@Composable
fun Example(navController: NavController) {
val vector = rememberVector()
Column(
생략
) {
Image(
imageVector = vector,
contentDescription = "Custom vector",
)
}
}