Android анимации. Часть 4. Управление анимацией VectorDrawable
В прошлой статье мы смогли анимировать SVG изображение, то есть vector, в нашем приложении. Однако встал вопрос, возможно ли управлять анимацией VectorDrawable?
Простая анимация. Animator - первая статья
Простая анимация. State View - вторая статья
Vector анимация. Стандартные решения – третья статья
VectorDrawable анимация. Управление анимацией. Хардкор – четвертая статья
Возьмем уже знакомые нам анимации из прошлой статьи и постараемся привязать их к прогрессу SeekBar. Контрол будем использовать из support библиотеки. Кроме этого нам понадобится сторонняя библиотека – RichPathView.
В activity_main.xml добавляем код:
<com.richpath.RichPathView
android:id="@+id/richPathView"
…
app:vector="@drawable/ic_play" />
<android.support.v7.widget.AppCompatSeekBar
android:id="@+id/seekBar"
…
android:max="100"
android:progress="0" />
Переходим в MainActivity.java. Дальше мы будем работать только там. Вставляем код:
RichPath richPauseTop = imageRichPath.findRichPathByName("pause1");
if (richPauseTop != null) {
pauseTop = new DynamicPath(richPauseTop,
"M 44 32 L 44 64 L 100 64 L 100 64 Z",
"M 32 40 L 32 56 L 96 56 L 96 40 Z",
90);
}
RichPath richPauseBottom = imageRichPath.findRichPathByName("pause2");
if (richPauseBottom != null) {
pauseBottom = new DynamicPath(richPauseBottom,
"M 44 96 L 44 64 L 100 64 L 100 64 Z",
"M 32 88 L 32 72 L 96 72 L 96 88 Z",
90);
}
Давайте рассмотрим, что мы здесь делаем. Вызовом функции imageRichPath.findRichPathByName("pause1") мы проводим поиск Path в ic_play.xml (в activity_main.xml мы его указали, как app:vector) по android:name. Единственный минус библиотеки RichPathView, он умеет работать только с Path, с Group работать не умеет.
Дальше мы проверяем, получилось ли найти Path, если да, то создаем DynamicPath для анимации. В параметрах мы передаем RichPath, а после несколько настроек – pathFrom, pathTo и rotation. Их мы берем из xml файлов, которые мы храним в res/animator.
Далее отслеживаем изменение ползунка seekBar:
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean b) {
if (pauseTop == null && pauseBottom == null)
return;
pauseTop.animate(progress);
pauseBottom.animate(progress);
}
…
});
Здесь все просто и понятно. Теперь самое интересное. Где нам взять DynamicPath? Этот класс я написал сам. Пока он в сыром виде, возможно, когда-то я доведу его до ума и даже сделаю библиотеку для совсем простой работы с подобной анимацией. Вставляем код перед последней ‘}’ в MainActivity.java.
private class DynamicPath {
private final String regEx = "((\\d+\\.\\d+)|(\\d+))";
private final Locale locale = Locale.ENGLISH;
private final RichPath richPath;
private int rotation;
private boolean isRotationAnimation = false;
private final String dynamicPathFrom;
private List<Float> pathDataFrom = new ArrayList<>();
private final String dynamicPathTo;
private List<Float> pathDataTo = new ArrayList<>();
DynamicPath(@NonNull RichPath richPath, String pathFrom, String pathTo, @Nullable Integer rotation) {
this.richPath = richPath;
if (rotation != null) {
this.rotation = rotation;
isRotationAnimation = true;
}
dynamicPathFrom = getDynamicPath(pathFrom, pathDataFrom);
dynamicPathTo = getDynamicPath(pathTo, pathDataTo);
}
private String getDynamicPath(String pathFrom, List<Float> pathDataFrom) {
Matcher m = Pattern.compile(regEx)
.matcher(pathFrom);
while (m.find()) {
try {
pathDataFrom.add(
new DecimalFormat("0.#", DecimalFormatSymbols.getInstance(locale))
.parse(m.group())
.floatValue());
} catch (ParseException e) {
e.printStackTrace();
}
}
return m.replaceAll("%f");
}
public void animate(int progress) {
if (dynamicPathFrom.length() > 0 &&
dynamicPathFrom.length() == dynamicPathTo.length() &&
pathDataFrom.size() > 0 &&
pathDataTo.size() > 0) {
if (!dynamicPathFrom.equals(dynamicPathTo))
throw new IllegalArgumentException("This animation not supported");
Float[] pathFrom = pathDataFrom.toArray(new Float[pathDataFrom.size()]);
Float[] pathTo = pathDataTo.toArray(new Float[pathDataTo.size()]);
List<Float> args = new ArrayList<>();
for (int i = 0; i < pathDataTo.size(); i++) {
args.add(calcMorph(pathFrom, pathTo, i, progress));
}
richPath.setPathData(String.format(locale, dynamicPathTo, args.toArray()));
}
if (isRotationAnimation)
richPath.setRotation((float) (richPath.getRotation() + (rotation * progress * 0.01)));
}
private float calcMorph(Float[] from, Float[] to, int position, int progress) {
return from[position] - ((from[position] - to[position]) * progress * 0.01f);
}
}
Здесь немного хардкора. Можете просто это скопировать :) Что получилось?
Если у вас есть идеи по улучшению или вопросы, пишите. Постараюсь ответить.