Android & Kotlin Tutorials

Learn Android development in Kotlin, from beginner to advanced.

Vector Graphics on Android

In this tutorial you will learn what Scalable Vector Graphics are and how to create and manipulate Vector Graphics on Android

5/5 6 Ratings

Version

  • Kotlin 1.3, Android 8.1, Android Studio 3

In this tutorial, you will work with scalable vector graphics (SVG) on Android to build an application that creates shareable images using baby-faced emojis that can be scaled and repositioned on a background image.

Vector graphics are an extremely powerful image format that both reduce your apk’s size and provide a more dynamic visual appearance that scales across the various screen densities within the Android ecosystem. You will learn about this format through the standard SVG pathspec commands available which will help you to understand the theory behind drawing them. You will also learn by using the related APIs in Android.

In addition, you will learn how to apply them to widgets and about the support Android provides for SVGs on lower API levels. Finally, you will wrap up the tutorial by learning how to animate a vector drawable to create a cool loading animation on the splash screen of the app.

Getting Started

Download the project materials using the Download Materials button at the top or bottom of this article. Then, import the project into Android Studio to get started.

Note: This tutorial assumes that you are generally familiar with building applications in Android Studio and working with assets to add resources to your project. You should also be familiar with the Kotlin programming language. If you need to brush up on any of these things, you can check out Beginning Android Development with Kotlin, Part One: Installing Android Studio or Kotlin For Android: An Introduction.

Build and run the app. The first screen you’ll see is one where you can place baby-faced emojis on a background. Try saving one.

Baby emoji screenshot

By clicking History in the toolbar, you can view your creation.

Baby emoji history screenshot

Great! Time to keep going.

What is a Vector Drawable

VectorDrawables are the SVG equivalent on Android. They are defined in XML using resource files and can be dynamically altered at runtime. The SVG format is an open standard image format defined by the World Wide Web Consortium (W3C) for creating scalable, interactive and animatable images through XML. While the API on Android is not a one-to-one match with the SVG format provided by the web, it offers the ability to draw individual paths, groups of paths, and create clipping masks to compose complex graphical assets for your applications. You can think of this process as drawing an image on paper using a pen or pencil.

Why do you need this type of image format though if Android supports the ability to serve different resources using the density resource buckets?

Well, to understand more you can compare with the older format, raster images.

Vector vs. Raster Graphics

Raster image formats (.BMP, .TIF, .JPG, .GIF, and .PNG) are what you probably have the most experience with in the past. These are grids of pixels that are a fixed size only containing color information. You can imagine how limiting this is compared to the vector format which describes the images as a series of points and paths composing shapes on an arbitrary grid size, which can also be colored dynamically. Raster images can be colored as well but the real difference comes when scaling them up. They become pixelated as they get larger and they lose their sharpness when compared to SVG. Look at the example below to see the difference when scaling.

raster vs vector graphics

Creating Assets Before Vector Drawables

In Android Studio, creating assets before vector drawables required working with designers to create a baseline image at a fixed size and then duplicate that image scaling it up for the different screen density buckets.

For example, to draw a share icon at 24×24 density independent pixels (dp) a designer would create a canvas at this pixel size and then draw the artwork within it. This would be the baseline graphic provided for the mdpi (160dpi) screen devices. Then the designer would need to create an additional canvas at 36×36 (1.5x for hdpi), 48×48 (2x for xhdpi), 72×72 (3x for xxhdpi) and 96×96 (4x for xxxhdpi) and scale or redraw the artwork to fit these bounds.

This was, however, only necessary for icons that Android Studio didn’t provide by default in its icon collection within Asset Studio (more on this in a bit). As you can infer, this process of providing assets for every screen density would bloat your app taking up more space on your user’s devices and increase the download time from the Play Store. This isn’t the best experience but it cannot always be avoided with Vector drawables as they are no silver bullet.

When to Apply Vector Drawables

Vector drawables can be applied in various places within your Android application where raster graphics used to be the default. For simple assets across your application like icons, they are the perfect solution to providing rich imagery scalable across different densities with minimal apk bloat. They can also be used to replace illustrations throughout your application. They cannot, however, be used to replace things like photos. As mentioned before, VectorDrawables uses XML to describe the paths, shapes and fills for an asset which gets inflated at runtime, so complex shapes can take some time to render on screen and take a performance hit on your application if they have many paths to draw.

Enough theory and information already. How do you create these VectorDrawables?!

Creating a Vector Drawable by Hand

Creating a vector by hand isn’t all that complex for simple graphics so that is what you will start with here. As previously mentioned, paths are drawn using a series of commands from the SVG pathspec much like drawing with a pencil on paper.

Consider creating the following asset:

smiling face vector graphic

You can accomplish this through Android by specifying it as a vector drawable.

Start by creating a file named ic_temp.xml in the res ‣ drawable folder. Then, paste the following XML into the file:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
  android:width="24dp"
  android:height="24dp"
  android:viewportWidth="24.0"
  android:viewportHeight="24.0">
    <!-- Groups and paths will go here-->
</vector>

Here you have defined a vector drawable with a virtual canvas of 24×24 pixels and a physical asset size of 24x24dp

Paths

Paths can be thought of as the main component for creating a vector asset. When you draw them, consider the pencil analogy again, you move your pencil to a point and put it down on the paper then draw a line or a bezier curve to another point repeating this process over and over until you compose your illustration. This is exactly how vectors are created only you specify them using the various path commands. So what are the available commands that Android supports for inflating these vector images?

Well, first you need to add the android:pathData="" attribute in XML.

Copy the following XML just below the opening vector tag:

<path android:pathData=""/>

Here you have defined your first path with an empty android:pathData value.

Before going any further, it is important to note that you won’t see any paths without adding a stroke width and stroke color or a stroke fill so time to do that next.

Fill and Stroke

As you could imagine, fill is just the information about what color to provide for the inside portion of the path. This is added using the android:fillColor="@color/fill_color" or android:fillColor="#FF777777" format in XML. Add the fillColor attribute to your path so it now looks like this:

<path 
  android:fillColor="#FF777777"
  android:pathData=""/>

Here you have added a gray fill color to your path.

With the android:strokeWidth="" and android:strokeColor="" XML attributes you can provide a stroke or outline to the vector drawable styling its width and color respectively like so:

Copy and paste the following attributes lines inside to the path tag right after the fillColor and pathData attributes:

android:strokeWidth="1"
android:strokeColor="#FF000000"

Here you have:

  1. Added a stroke width of 1 to your path.
  2. Added a stroke color to your path.

Next, you will see how to actually draw a vector using its built-in functionality from Android which follows the scalable vector graphics path spec.

Drawing Commands

The available drawing commands which you will learn next are:

  • M: Move to.
  • L: Line to.
  • C: Bezier curve to, or curve line.
  • Z: End line.

Move to (M)

The move to command is denoted by a capital M and means within the grid move to an x,y coordinate.

Copy M2,12 into pathData. It should now look like this:

<path 
  android:fillColor="#FF777777"
  android:pathData="M2,12" 
  android:strokeWidth="1"
  android:strokeColor="#FF000000"/>

Here you move the pencil to 2 points from the left edge (x-axis) and 12 points from the top edge (y-axis)

This is because the canvas starts from the top left at coordinate 0,0 so moving to the left or right decreases and increases the X position and moving down or up decreases or increases the Y position respectively.

Note: X and Y coordinates can contain decimal places for precise placement within the grid.

Line to (L)

The line to command is denoted by a capital L and means draw a line from the previous point to this X, Y coordinate. For example, in the same 24×24 grid above L22,12 would draw a line all the way across the canvas until 2 points from the right edge.

Copy L22,12 into the path data right after M2,12. The pathData now looks like this:

android:pathData="M2,12 L22,12"

Here you have drawn a horizontal line from 2,12 to 22,12 within the 24×24 grid.

Note: By opening the Preview pane in Android Studio, you can get a peek at what you’re drawing.

Preview pane in Android Studio

Bezier Curve to (C)

Paths can also support curved lines using the curve to command which draws a bezier curve from the previous point to a final point using 2 control coordinates. A bezier curve is a smooth curve that can be created mathematically or from a series of instructions. It is denoted by Cx1,y1 x2,y2 finalX,finalY where x1,y1 is the control point for the start of the curve, x2,y2 is the control point for the end of the curve, and finalX,finalY is the point you want the curve to end. The control points help describe the way the curve should bend. You can learn more here.

For example (again using the 24×24 grid), you could write C22,22 12,22 12,22 C12,22 1,22 2,13 and this would draw a curved line from the right edge back to the left edge creating a semi-circle shape on the lower half of the grid. Add that command to your path. The full pathData should now look like this:

android:pathData="M2,12 L22,12 C22,22 12,22 12,22 C12,22 1,22 2,13"

If you break down this command you can see you are drawing 2 bezier curves:

  1. C22,22 12,22 12,22 – draws a curve from the previous point to 12,22 in the center using 22,22 as control point one and 12,22 as control point 2.
  2. C12,22 1,22 2,13 – draws a curve from the previous point to 2,13 using 12,22 as control point one and 1,22 as control point 2.

The final step to complete a vector’s path data is to close the path.

Closing a Path (Z)

Finally, to close a path you simply use the Z command which closes the path drawing a straight line from the current position to the starting point.

For example, to stop drawing at this point 2,13:

Add Z to the end of your pathData so it looks like this:

android:pathData="M2,13 L22,13 C22,22 12,22 12,22 C12,22 1,22 2,13Z"

By now you have created the path data for the semi-circle on the bottom portion of the screen. Notice that the red points along the bottom line in this image are the control points of your curves.

filled semi circle vector graphic

This is pretty cool but what if you wanted to add another path or combine two paths so you could do something with them? This is where groups come into play.

Groups

Groups are just that, groups of paths. They can be used to combine paths into the same layer which allows you to both stay organized and do other things like rotating, scaling, translating or animating multiple components at once.

You will do this in a bit. For now, consider the following XML group which builds on the previous example and draws a couple of square eyes inside a group.

Copy the following code and paste it above the previously defined path tag:

<!-- 1 -->
<group>

  <!-- 2 -->
  <path
    android:fillColor="#FF777777"
    android:pathData="M2,2 L 2,11 L 11,11 L 11,2Z"
    android:strokeWidth="1"
    android:strokeColor="#FF000000" />

  <!-- 3 -->
  <path
    android:fillColor="#FF777777"
    android:pathData="M13,2 L 13,11 L 22,11 L 22,2Z"
    android:strokeWidth="1"
    android:strokeColor="#FF000000" />
</group>

Here you have:

  1. Added a new group with two sub paths inside it.
  2. Added the first rectangular eye. left rectangle vector graphic
  3. Added the second rectangular eye. two rectangles vector graphic

The final XML should look like:

<vector xmlns:android="http://schemas.android.com/apk/res/android"
  android:width="24dp"
  android:height="24dp"
  android:viewportWidth="24.0"
  android:viewportHeight="24.0">
  <!-- 1 -->
  <group>
    <path
      android:fillColor="#FF777777"
      android:pathData="M2,2 L 2,11 L 11,11 L 11,2Z"
      android:strokeWidth="1"
      android:strokeColor="#FF000000" />

    <path
      android:fillColor="#FF777777"
      android:pathData="M13,2 L 13,11 L 22,11 L 22,2Z"
      android:strokeWidth="1"
      android:strokeColor="#FF000000" />
  </group>

  <!-- 2 -->
  <path
    android:fillColor="#FF777777"
    android:pathData="M2,13 L22,13 C22,22 12,22 12,22 C12,22 1,22 2,13Z"
    android:strokeWidth="1"
    android:strokeColor="#FF000000" />
</vector>
  1. Group of 2 paths, composing the eyes.
  2. The semi-circle path, composing the smile.

full image with points vector graphic

Uppercase vs. Lowercase Commands: Using Relative Path Commands

An upper-case command is positioned absolutely on the grid whereas a lower-case command is positioned relatively within the grid. This can be a bit confusing to understand at first but consider an example. You want to draw a 9×9 square in a 10×10 grid with one-pixel margin.

Starting from the upper-left-hand-side of a 10×10 grid, you perform a moveto command to start at the absolute coordinate: M1,1. For relative commands, each one will be based on the position of the previous point. For example, consider: l8,0. Since this is a relative lineto command, this would draw a line 8 points over on the x-axis from 1,1 resulting in a line to 9,1 in the grid. The next point in the grid would then be relative to this new point 9,1. So, to draw a vertical line down from 9,1 to 9,9 add the following to the path: l0,8. And finally, square the box at 1,9 by adding a lineto command of l-8,0. Close the path with a z: M1,1 l8,0 l0,8 l-8,0z.

That completes the 8×8 square. :]

drawing a vector graphic with a relative progression

See, not so bad!

All right, so these assets don’t look amazing but in the next section you will see how to use Android Studio’s Asset Studio to create a vector drawable that looks much better than drawing it by hand. And better yet you will now know exactly what you are looking at when Android Studio generates the path data for you. It is not typical that you would draw something like a vector by hand as this would be the job of your designer. The Asset Studio wizard would then allow you to import the exported SVG from the file. [:

Creating a VectorDrawable in Android Asset Studio

Creating a VectorDrawable using the Asset Studio is as easy as following the steps within the wizard to choose an icon, specify a color and select the width and height of the asset. As previously mention, Android comes out of the box with many assets, most of which can be found at the material.io website.

It is time to start adding these icons into your application. Since you will be creating a scene of baby emojis on a background image in this application, you will need a couple of icons: a save icon, a delete icon and a history icon.

To add an icon:

  1. Right-click the drawable folder.
  2. Follow the menu to New ‣ Vector Asset.

This will pull up the following window:

To select the icon, simply click the clipart button and search for save in the search box, select the icon and click OK.

Leave all other defaults and click Next, then Finish.

Repeat this for the search term delete, share and history and import these icons too.

Great! Now you have the icons you will be needing for the various UI elements you will be adding vector assets to.

Adding Vector Assets to UI Components

The Android framework supports adding vector assets to the following components:

  • ImageView
  • Button
  • ImageButton
  • FloatingActionButton
  • TextView
  • MenuItem
  • CheckBox

Pretty much any widget that either inherits from ImageView or supports adding an image source supports the vector format.

Open up layout ‣ row_history.xml and paste the following line into the ImageButton with id historyShareImageButton:

app:srcCompat="@drawable/ic_share_black_24dp"

Here you have added your first vector drawable to an ImageButton.

Share icon on history list item

Next, open up acitivty_main.xml and add the drawable to the button with the id deleteButton in the bottom bar near the bottom of the file:

app:icon="@drawable/ic_delete_black_24dp"

And then this drawable to the saveButton button:

app:icon="@drawable/ic_save_black_24dp"

Here you have:

  1. Added the ic_delete_black_24dp icon.
  2. Added the ic_save_black_24dp icon.

Rebuild and run the app, and you will see the icons displaying on the buttons in the bottom of the screen.

bottom bar items with new icons

Next, add the following line to the menu ‣ main_menu.xml in the action_history item:

android:icon="@drawable/ic_history_black_24dp"

Here you have added the history icon to a MenuItem.

Rebuild and run the app, you will see the History text is now replaced with the icon in the Toolbar.

Toolbar with history icon

Tinting a VectorDrawable

With VectorDrawables you can also use one resource mutliple times and just alter its color with a tint. The vector tag in XML supports this through ther android:tint="" attribute where you can specify either a color code or a color resource.

Programmatically, you can get a reference to the VectorDrawable and call setTintList(ColorStateList tint) to change its color. The tint uses the BlendMode.SRC_IN by default but you can change this behavior as well in XML by using the android:tintMode="" attribute or programmatically by calling setTintBlendMode(BlendMode blendMode).

A powerful way to make use of the tint attribute on a vector in your app is by attaching a themed color resource to it in XML.

To do this, copy the following attribute onto the vector element in ic_share_black_24dp.xml file:

android:tint="?android:colorAccent">

Here you have applied the theme's accentColor to the vector drawable. Rebuild and run the app to see the results. By saving an image, and going to the history screen.

Do the same thing again, adding android:tint="?android:colorControlNormal" to ic_history_black_24dp.xml.

Scaling a VectorDrawable Programmatically

To scale your baby emojis, all you need to do is change the scaleX and scaleY properties of the ImageView. To do this, open the EmojiImageView.kt file and copy the code into the top of onScale() of the gesture detector on line 72. Use the kotlin.math.* import when prompted:

  // 1
  scaleFactor *= detector.scaleFactor
  // 2
  scaleFactor = max(0.1f, min(scaleFactor, 50f))
  // 3
  view.scaleX = scaleFactor
  view.scaleY = scaleFactor
  return true

Here you have:

  1. Computed the scale factor
  2. Reduced the scale factor to a value between 0.1f and 50f
  3. Applied the scale factore to scaleX and scaleY of the view

Build and re-run the app, and you should now be able to move the baby-emojis around the screen and scale them. Cool! [:

Animated Vector Drawables

The AnimatedVectorDrawable class adds animation functionality to vector drawables. This means you can use them to change properties of a vector drawable using the property animations built into Android. Animating a vector drawable is done in two ways currently: Mutilple XML Files or a Single XML File.

You will work with the first approach here, but to read more about the single file approach you can go to the docs.

The multiple file apprach involves defining three things:

  1. The vector drawable in XML.
  2. The animations to run in XML.
  3. The animated vector drawable in XML.

So what does this look like?

Creating a Loader Using AnimatedVectorDrawable

Start by creating a file called ic_vd_loader.xml in the drawable folder and paste in the following vector drawable:

<!-- 1 -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
  android:width="72dp"
  android:height="24dp"
  android:viewportWidth="72"
  android:viewportHeight="24">

  <!-- 2 -->
  <group
    android:name="scaleGroup"
    android:pivotX="12"
    android:pivotY="12"
    android:scaleX="1"
    android:scaleY="1">
    <path
      android:fillColor="#D8D8D8"
      android:fillType="evenOdd"
      android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
      android:strokeWidth="1"
      android:strokeColor="#00000000" />
  </group>

  <!-- 3 -->
  <group
    android:name="translateGroup"
    android:translateX="1"
    android:translateY="1">
    <path
      android:fillColor="#D8D8D8"
      android:fillType="evenOdd"
      android:pathData="M28,4h16v16h-16z"
      android:strokeWidth="1"
      android:strokeColor="#00000000" />
  </group>

  <!-- 4 -->
  <group
    android:name="rotationGroup"
    android:pivotX="60"
    android:pivotY="14"
    android:rotation="0">
    <path
      android:fillColor="#D8D8D8"
      android:fillType="evenOdd"
      android:pathData="M60,4l8,16l-16,0z"
      android:strokeWidth="1"
      android:strokeColor="#00000000" />
  </group>
</vector>

Here you have:

  1. Added the vector with a virtual canvas of 72x24.
  2. Added a group named scaleGroup which draws a circle.
  3. Added a group named translateGroup which draws a square.
  4. Added a group named rotationGroup which draws a triangle image.

Next, create a few animations files in the anim folder.
Create a file named anim_rotate.xml and paste in the following XML:

<set xmlns:android="http://schemas.android.com/apk/res/android">
  <!-- 1 -->
  <objectAnimator
    android:duration="700"
    android:propertyName="rotation"
    android:valueFrom="0"
    android:valueTo="360"
    android:repeatCount="infinite"
    android:repeatMode="restart"
    android:valueType="floatType" />
</set>

Here you have added an ObjectAnimator in XML for the rotation property repeating a full rotation infinitely.

Create another file called anim_scale.xml and paste in the following XML:

<set xmlns:android="http://schemas.android.com/apk/res/android"
  android:shareInterpolator="true">
  <!-- 1 -->
  <objectAnimator
    android:duration="500"
    android:propertyName="scaleX"
    android:valueFrom="0"
    android:valueTo="1"
    android:repeatCount="infinite"
    android:repeatMode="reverse"
    android:valueType="floatType" />

  <!-- 2 -->
  <objectAnimator
    android:duration="500"
    android:propertyName="scaleY"
    android:valueFrom="0"
    android:valueTo="1"
    android:repeatCount="infinite"
    android:repeatMode="reverse"
    android:valueType="floatType" />
</set>

Here you have:

  1. Added an ObjectAnimator in XML for the scaleX property
  2. Added an ObjectAnimator in XML for the scaleY property

Both, repeating infinitely in reverse.

Create one more file called anim_translate.xml and paste in the following XML:

<set xmlns:android="http://schemas.android.com/apk/res/android">
  <!-- 1 -->
  <objectAnimator
    android:duration="700"
    android:propertyName="translateY"
    android:repeatCount="infinite"
    android:repeatMode="reverse"
    android:valueFrom="0"
    android:valueTo="12"
    android:valueType="floatType" />

  <!-- 1 -->
  <objectAnimator
    android:duration="500"
    android:propertyName="scaleY"
    android:repeatCount="infinite"
    android:repeatMode="reverse"
    android:valueFrom=".5"
    android:valueTo="1"
    android:valueType="floatType" />
</set>

Here you have:

  1. Added an ObjectAnimator in XML for the translateY property.
  2. Added an ObjectAnimator in XML for the scaleY property.

Again, both repeating infinitely in reverse.

The last piece of the puzzle is the vector drawable XML file. In the drawable folder, create a file named loading_animation.xml and copy the following XML:

<!-- 1 -->
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
  android:drawable="@drawable/ic_vd_loader">
  <!-- 2 -->
  <target
    android:name="scaleGroup"
    android:animation="@anim/anim_scale" />
  <!-- 3 -->
  <target
    android:name="rotationGroup"
    android:animation="@anim/anim_rotate" />
  <!-- 4 -->
  <target
    android:name="translateGroup"
    android:animation="@anim/anim_translate" />
</animated-vector>

Here you have:

  1. Added a animated-vector tag referencing to the vector drawable ic_vd_loader.xml.
  2. Added a target for the scaleGroup to use anim_scale.xml animation.
  3. Added a target for the rotationGroup to use anim_rotate.xml animation.
  4. Added a target for the translateGroup to use anim_translate.xml animation.

Almost done!

Now, open the layout/activity_splash.xml file and paste in this ImageView below the TextView:

  <ImageView
    android:id="@+id/loadingView"
    android:layout_width="0dp"
    android:layout_height="wrap_content"
    android:layout_marginTop="24dp"
    app:srcCompat="@drawable/loading_animation"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/textView"/>
Note: Don't forget to replace the app:layout_constraintBottom_toBottomOf="parent" with app:layout_constraintBottom_toTopOf="@id/loadingView" on the TextView!

Finally, open the SplashActivity and add the following code just below the call to setContentView() in onCreate():

val animatable = loadingView.drawable as 
    android.graphics.drawable.Animatable
animatable.start()

Build and re-run the app and observe your awesome new loading animation!

Supporting Older API Versions

Vector drawables are supported back to API 21 by default but you can support them back to API 14 using Jetpack. This can be configured in your app level build.gradle file.

Add the following line to the defaultConfig of the app level build.gradle:

android {
   defaultConfig {
     // 1
     vectorDrawables.useSupportLibrary = true
   }
}

Here you have enabled the support library back to API 14.

To complete the process, anywhere you use VectorDrawable or AnimatedVectorDrawable you can replace them using the relevant VectorDrawableCompat and AnimatedVectorDrawableCompat classes. This, however, is not absolutely necessary for this project to support older API versions since you don't reference them in code.

Where to Go From Here?

Wow, that was a lot to chew on but you are now an expert on the vector image format in Android! [; In this tutorial, you did not see how you can clip paths and create masks with vector drawables. These are pretty powerful so you should definitely explore more about them.

Additionally, you covered a very simple animation with regard to AnimatedVectorDrawables and there are a lot more ways to make powerful animations such as using the ShapeShifter tool which will introduce you to create path morphing animations. You can also look further into the pathspec for anything you didn't get to try out here!

If you had trouble following along or just want to check it out, import the VectorDrawables-Finished project into Android Studio and run it! You can find this by using the Download Materials button at the top or bottom of this tutorial.

Please leave comments below with any questions and thanks for reading!

Average Rating

5/5

Add a rating for this content

6 ratings

Contributors

Comments