Reputation: 4125
It looks like the only way to go about loading custom icons from Android Vector Resources in the res
folder is to do it within a @Composable
function using the vectorResource(R.drawable.myVectorName)
method.
This is great and all, but I like the syntax of fetching VectorAssets
for the Icon(asset: VectorAsset)
class, which looks like Icon(Icons.Default.Plus)
.
It looks like the vectorResource()
method uses an internal method called loadVectorResource()
, and the methods it uses to read the actual XML file composing the vector asset file are also internal.
How would I go about creating an object like MyAppIcons.Default.SomeIcon
in Jetpack Compose?
EDIT
So, I have sort-of found a solution. However, it would be nice to make my own extension/overloading of the built-in Icon()
function, but I'm not sure if there is a proper way to do this.
Upvotes: 19
Views: 24954
Reputation: 2473
The approach that worked for me was adding a @Composable
getter to the fields, like so:
object Icons {
val Close: VectorIcon
@Composable get(): ImageVector.vectorResource(R.drawable.close)
}
Those can then be used in way consistent with the rest of the compose framework, eg:
Icon(Icons.Close, contentDescription = "Close")
Upvotes: 2
Reputation: 2709
I went down the other route and extracted the logic from Jetpack Compose source code that turns an XML SVG path string into an ImageVector
. In the end I came up with this:
fun makeIconFromXMLPath(
pathStr: String,
viewportWidth: Float = 24f,
viewportHeight: Float = 24f,
defaultWidth: Dp = 24.dp,
defaultHeight: Dp = 24.dp,
fillColor: Color = Color.White,
): ImageVector {
val fillBrush = SolidColor(fillColor)
val strokeBrush = SolidColor(fillColor)
return ImageVector.Builder(
defaultWidth = defaultWidth,
defaultHeight = defaultHeight,
viewportWidth = viewportWidth,
viewportHeight = viewportHeight,
).run {
addPath(
pathData = addPathNodes(pathStr),
name = "",
fill = fillBrush,
stroke = strokeBrush,
)
build()
}
}
All you have to do is call this function with pathStr
set to the value of android:pathData
from the drawable XML file. Here's an example:
val AppleIcon by lazy { makeAppleIcon() }
// by Austin Andrews, found on https://materialdesignicons.com/
private fun makeAppleIcon(): ImageVector {
return makeIconFromXMLPath(
pathStr = "M20,10C22,13 17,22 15,22C13,22 13,21 12,21C11,21 11,22 9,22C7,22 2,13 4,10C6,7 9,7 11,8V5C5.38,8.07 4.11,3.78 4.11,3.78C4.11,3.78 6.77,0.19 11,5V3H13V8C15,7 18,7 20,10Z"
)
}
@Preview
@Composable
fun AppleIconPreview() {
Surface {
Icon(AppleIcon, "Apple")
}
}
Upvotes: 4
Reputation: 1139
from Resources in Compose
Use the painterResource
API to load either vector drawables or rasterized asset formats like PNGs. You don't need to know the type of the drawable, simply use painterResource
in Image
composables or paint
modifiers.
// Files in res/drawable folders. For example:
// - res/drawable-nodpi/ic_logo.xml
// - res/drawable-xxhdpi/ic_logo.png
// In your Compose code
Icon(
painter = painterResource(id = R.drawable.ic_logo),
contentDescription = null // decorative element
)
Upvotes: 28
Reputation: 4125
Turns out I wasn't using my brain. The answer is pretty easy.
The gist is, Icon()
is a composable function, meaning that of course vectorResource() can be used there.
So, the correct approach is no secret... it's to make your own MyAppIcon()
component, call vectorResource()
and then return a normal Icon()
, like so:
Correct Way
@Composable
fun MyAppIcon(
resourceId: Int,
modifier: Modifier = Modifier,
tint: Color = AmbientContentColor.current
) {
Icon(
asset = vectorResource(id = resourceId),
modifier = modifier,
tint = tint
)
}
You can then create an object elsewhere, like so:
object MyAppIcons {
val SomeIcon = R.drawable.someIcon
val AnotherIcon = R.drawable.anotherIcon
}
When you put the two together, you can use it like this:
MyAppIcon(MyAppIcons.SomeIcon)
I'm hoping that Google just adds this override soon, allowing us to pass in resource IDs.
Upvotes: 18
Reputation: 63
There is a way to load asset using Icon(Icons.Default.Plus)
. You need to make an extesion property
val androidx.compose.material.icons.Icons.Filled.FiveG : VectorAsset
get() {
}
but I don't see the way to get VectorAsset
outside of composable function.
Of course you can do something like this
val androidx.compose.material.icons.Icons.Filled.FiveG : VectorAsset
get() {
return Assets.FiveG
}
object Assets {
lateinit var FiveG: VectorAsset
}
@Composable
fun initializeAssets() {
Assets.FiveG = vectorResource(R.drawable.ic_baseline_5g_24)
}
but it's a bad idea to have a composable with side effect. So i'm waiting for someone to find a way to convert SVG to VectorAsset
Kotlin class or get VectorAsset
object outside of composable function.
Upvotes: 1