Introduction
If like me, you have always wondered what is the difference between viewportWidth
and width
(or viewportHeight
and height
) in vector drawables and you tend to randomly tweak these attributes until your image looks right, this post is for you.
I will show you how these attributes differ using a simple example and some visualizations.
Example
To illustrate the difference, I created a simple app with one screen that contains a centered image filling as much content as it needs. Here is the code (in Jetpack Compose):
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = R.drawable.rectangle),
contentDescription = null
)
}
}
}
}
}
And this is the code for the R.drawable.rectangle
drawable:
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="36"
android:viewportHeight="36">
<path
android:fillColor="#FFEB3B"
android:pathData="M0,0 L36,0 L36,36 L0,36 z" />
<path
android:fillColor="#3F51B5"
android:pathData="M2,2 L34,2 L34,34 L2,34 z" />
</vector>
If you need a refresher on how to interpret commands like M2,2
in pathData
, here is a great post on Ray Wenderlich’s blog: link. But to give you a quick overview:
M2,0
means Move to the point at position 2,0 (x,y).L36,0
means draw a Line from the current point to the point at 36,0 (x,y).z
closes the path by drawing a straight line from the current position to the starting point.
Remember that 0,0 position is in the upper left corner. Moving to the right increases the X position and moving down increases the Y position (contrary to what you might be used to).
Both paths in the drawable above define a rectangle. The first path draws a yellow rectangle that covers the entire available space, while the second path draws a slightly smaller blue rectangle on top of the first one.
This is what the rendered image looks like:
As you can see, height
and width
attributes were both set to “256dp” and viewportWidth
, as well as viewportHeight
, to “36”.
Notice that height
and width
values end with “dp”. It’s because they define the intrinsic size of the drawable. In simpler words, if you give this drawable to an ImageView
that covers as much space as it needs, that ImageView
will have the exact same size as the drawable. That’s why in the screenshot above, the rendered image has a size of 256dp x 256dp (just like height
and width
values).
Conversely, viewportWidth
and viewportHeight
define the canvas size upon which we draw our pixels. All of our commands in pathData
use coordinates from this space.
Let’s take a look at the pathData
for our blue rectangle:
android:pathData="M2,2 L34,2 L34,34 L2,34 z"
As you can see, all commands here use points that are contained in a space defined by viewportWidth
and viewportHeight
(which is 36 x 36).
Below I marked some of the relevant coordinates:
Now let’s see what happens when I change viewportWidth
and viewportHeight
to “48” (and modify the yellow rectangle’s pathData
to still cover the entire space as in the previous example).
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="48"
android:viewportHeight="48">
<path
android:fillColor="#FFEB3B"
android:pathData="M0,0 L48,0 L48,48 L0,48 z" />
<path
android:fillColor="#3F51B5"
android:pathData="M2,2 L34,2 L34,34 L2,34 z" />
</vector>
Here is the rendered result:
The image takes the same amount of space on the screen as before, but the blue rectangle is now smaller. This is because the width
and height
values stayed the same (“256dp”), but the coordinate space has changed affecting all of our commands responsible for drawing our paths.
This can be better seen in the below image with highlighted points:
Summary
To summarize, the difference between size and viewport size is that the former reports its values (width
and height
) to the outside world, whereas the latter defines an “internal” space that is the coordinate space of our pathData
.
If you are given a vector drawable (or svg) from a designer, remember not to change viewportWidth
and viewportHeight
attributes. They define the size of the canvas and all pathData
commands use this coordinate space. If you change them, your image might become distorted. Instead, if you want to manipulate the image’s size, just use width
and height
.