April 15, 2020

Векторная анимация в Android (часть 2)

Прежде чем приступить к анимации, необходимо понять, какие возможности нам предлагает Android. Типы анимации в Android можно условно разделить на базовую и комплексную.

К базовой анимации можно отнести простую манипуляцию параметрами объекта, такие как положение, масштаб, поворот и опорная точка (точка, относительно которой происходит анимация). Такую анимацию мы можем применять только к Group внутри VectorDrawable (кроме того, такие типы анимации применимы и к любым View, например: Button, TextView и т.д, но в статье мы будем рассматривать базовую анимацию только применительно к векторным объектам).

К комплексной анимации можно отнести анимацию, применяемую только к Path в VectorDrawable: манипуляция цветом заливки и обводки, альфой, толщиной обводки, тримминг обводки и изменение геометрии (морфинг). Все эти свойства и их значения описываются внутри ObjectAnimator. ObjectAnimator предназначен для анимации только одного свойства. Для анимации нескольких свойств добавляются соответствующие аниматоры, которые можно объединить в set, но об этом позже.

Схема поможет разобраться в том, какую анимацию к чему мы можем применять.

Условная структура XML Bundle

В первом ObjectAnimator перечислены свойства: fillColor, fillAlpha, strokeColor, strokeAlpha, strokeWidth, trimPathStart, trimPathEnd, trimPathOffset, pathData, которые мы можем применять только к Path и Clip-Path. Во втором ObjectAnimator свойства, применяемые только к Group: Alpha, translateX, translateY, scaleX ,scaleY, rotation, pivotX, pivotY.

Свойства применяемые к Path и Clip-Path
Свойства применяемые к Group

Простая анимация

Начнем с базовой векторной анимации. Попробуем сделать анимацию открытия и закрытия замка. Ранее мы уже подготовили файл для такой анимации, разделив иконку на 2 группы. Подробнее про подготовку файла можно прочитать в первой части статьи.

Исходный lock.xml выглядит так:

<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportHeight="24"
    android:viewportWidth="24">
    <path
        android:fillColor="#000000"
        android:pathData="M6,9c-1.1,0-2,0.9-2,2v10c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V11c0-1.1-0.9-2-2-2H6z
            M12,18c-1.1,0-2-0.9-2-2s0.9-2,2-2 s2,0.9,2,2S13.1,18,12,18z" />
    <path
        android:fillColor="#000000"
        android:pathData="M12,2C9.2,2,7,4.2,7,7v5h1.9V7c0-1.7,1.4-3.1,3.1-3.1s3.1,1.4,3.1,3.1v2H17V7C17,4.2,14.8,2,12,2z" />
</vector>

Сначала переведем статичный вектор в анимированный, обернув все в тэг animated-vector и добавив appt:attr тэги, для того чтобы собрать xml bundle. Чтобы анимировать дужку замка, необходимо первый path обернуть в group и присвоить ему уникальное имя (например, shackle).

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt">
    <aapt:attr name="android:drawable">
        <vector xmlns:android="http://schemas.android.com/apk/res/android"
            android:width="24dp"
            android:height="24dp"
            android:viewportHeight="24"
            android:viewportWidth="24">
            <path
                android:fillColor="#000000"
                android:pathData="M6,9c-1.1,0-2,0.9-2,2v10c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V11c0-1.1-0.9-2-2-2H6z
            M12,18c-1.1,0-2-0.9-2-2s0.9-2,2-2 s2,0.9,2,2S13.1,18,12,18z" />
            <group android:name="shackle">
                <path
                    android:fillColor="#000000"
                    android:pathData="M12,2C9.2,2,7,4.2,7,7v5h1.9V7c0-1.7,1.4-3.1,3.1-3.1s3.1,1.4,3.1,3.1v2H17V7C17,4.2,14.8,2,12,2z" />
            </group>
        </vector>
    </aapt:attr>
</animated-vector>

Затем создадим target внутри animated-vector, в котором укажем название группы, которую будем анимировать: shakle. Внутри него создадим ObjectAnimator, который будет отвечать за анимацию открытия дужки, используя свойство translateY. В propertyName указывается свойство, которое мы собираемся анимировать. В нашем случае это translateY. Свойство duration — время выполнения анимации в миллисекундах. В valueFrom и valueTo указываются начальные и конечные значения в dp соответственно. valueType в нашем случае имеет значение floatType, а если бы мы анимировали значения цвета, то был бы colorType, а для морфинга использовался бы pathType.

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt">
    <aapt:attr name="android:drawable">
        <vector xmlns:android="http://schemas.android.com/apk/res/android"
            android:width="24dp"
            android:height="24dp"
            android:viewportHeight="24"
            android:viewportWidth="24">
            <path
                android:fillColor="#000000"
                android:pathData="M6,9c-1.1,0-2,0.9-2,2v10c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V11c0-1.1-0.9-2-2-2H6z
            M12,18c-1.1,0-2-0.9-2-2s0.9-2,2-2 s2,0.9,2,2S13.1,18,12,18z" />
            <group android:name="shackle">
                <path
                    android:fillColor="#000000"
                    android:pathData="M12,2C9.2,2,7,4.2,7,7v5h1.9V7c0-1.7,1.4-3.1,3.1-3.1s3.1,1.4,3.1,3.1v2H17V7C17,4.2,14.8,2,12,2z" />
            </group>
        </vector>
    </aapt:attr>
    <target android:name="shackle">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:duration="1000"
                android:interpolator="@android:anim/accelerate_decelerate_interpolator"
                android:propertyName="translateY"
                android:repeatCount="infinite"
                android:valueFrom="0"
                android:valueTo="-2"
                android:valueType="floatType" />
        </aapt:attr>
    </target>
</animated-vector>

В результате у нас получилась простая анимация открытия замка. Для того чтобы добавить анимацию закрытия, необходимо создать новый objectAnimator. Для применения нескольких анимаций к одному target лучше всего использовать set.

Пример другой анимации с использованием set.

<?xml version="1.0" encoding="utf-8"?>
<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:aapt="http://schemas.android.com/aapt">
    <aapt:attr name="android:drawable">
        <vector
            android:width="48dp"
            android:height="24dp"
            android:viewportHeight="24"
            android:viewportWidth="48">
            <group android:name="gear_group">
                <path
                    android:name="gear_path"
                    android:fillColor="#FF1744"
                    android:pathData="M19.43 12.98c.04-.32 .07 -.64 .07 -.98s-.03-.66-.07-.98l2.11-1.65c.19-.15 .24
-.42 .12 -.64l-2-3.46c-.12-.22-.39-.3-.61-.22l-2.49
1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46 .18
-.49 .42 l-.38 2.65c-.61 .25 -1.17 .59 -1.69 .98 l-2.49-1c-.23-.09-.49 0-.61 .22
l-2 3.46c-.13 .22 -.07 .49 .12 .64 l2.11 1.65c-.04 .32 -.07 .65 -.07 .98 s.03
.66 .07 .98 l-2.11 1.65c-.19 .15 -.24 .42 -.12 .64 l2 3.46c.12 .22 .39 .3 .61
.22 l2.49-1c.52 .4 1.08 .73 1.69 .98 l.38 2.65c.03 .24 .24 .42 .49 .42 h4c.25 0
.46-.18 .49 -.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.23 .09 .49 0
.61-.22l2-3.46c.12-.22 .07 -.49-.12-.64l-2.11-1.65zM12 15.5c-1.93
0-3.5-1.57-3.5-3.5s1.57-3.5 3.5-3.5 3.5 1.57 3.5 3.5-1.57 3.5-3.5 3.5z" />
            </group>
        </vector>
    </aapt:attr>
    <target android:name="gear_path">
        <aapt:attr name="android:animation">
            <objectAnimator
                android:name="color"
                android:duration="2000"
                android:interpolator="@android:anim/accelerate_decelerate_interpolator"
                android:propertyName="fillColor"
                android:repeatCount="infinite"
                android:repeatMode="reverse"
                android:valueFrom="#FF1744"
                android:valueTo="#4527A0"
                android:valueType="colorType" />
        </aapt:attr>
    </target>
    <target android:name="gear_group">
        <aapt:attr name="android:animation">
            <set xmlns:android="http://schemas.android.com/apk/res/android"
                android:ordering="together">
                <objectAnimator
                    android:duration="0"
                    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
                    android:propertyName="pivotX"
                    android:valueFrom="12"
                    android:valueTo="12"
                    android:valueType="floatType" />
                <objectAnimator
                    android:duration="0"
                    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
                    android:propertyName="pivotY"
                    android:valueFrom="12"
                    android:valueTo="12"
                    android:valueType="floatType" />
                <objectAnimator
                    android:duration="2000"
                    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
                    android:propertyName="rotation"
                    android:repeatCount="infinite"
                    android:repeatMode="reverse"
                    android:valueFrom="0"
                    android:valueTo="160"
                    android:valueType="floatType" />
                <objectAnimator
                    android:duration="2000"
                    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
                    android:propertyName="translateX"
                    android:repeatCount="infinite"
                    android:repeatMode="reverse"
                    android:startOffset="0"
                    android:valueFrom="0"
                    android:valueTo="24"
                    android:valueType="floatType" />
            </set>
        </aapt:attr>
    </target>
</animated-vector>

Свойство ordering отвечает за порядок воспроизведения анимации в set. Может принимать два значения: together (одновременное воспроизведение — значение по умолчанию), sequentially (последовательное воспроизведение). Помимо этого, можно использовать свойство repeatMode для указания типа повтора анимации, а repeatCount указывает количество повторов. Стоит учитывать, что если в set используется последовательное воспроизведение, то repeatCount=infinite будет работать только в первом objectAnimator, так как очередь до остальных никогда не дойдёт.

Все приведенные примеры доступны для самостоятельного изучения в проекте на GitHub.

Источник: Векторная анимация в Android (часть 2)