Server sync... Block time in database: 1634995686, server time: 1653326594, offset: 18330908

Android анимации. Часть 4. Управление анимацией VectorDrawable


В прошлой статье мы смогли анимировать SVG изображение, то есть vector, в нашем приложении. Однако встал вопрос, возможно ли управлять анимацией VectorDrawable?

Простая анимация. Animator - первая статья
Простая анимация. State View - вторая статья
Vector анимация. Стандартные решения – третья статья
VectorDrawable анимация. Управление анимацией. Хардкор – четвертая статья

Возьмем уже знакомые нам анимации из прошлой статьи и постараемся привязать их к прогрессу SeekBar. Контрол будем использовать из support библиотеки. Кроме этого нам понадобится сторонняя библиотека – RichPathView.

В activity_main.xml добавляем код:

<com.richpath.RichPathView

    …
     />

<android.support.v7.widget.AppCompatSeekBar

    …

     />

Переходим в 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 

        if (rotation  {
            this.rotation 
            isRotationAnimation 
        }

        dynamicPathFrom  pathDataFrom);
        dynamicPathTo  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);
    }
}

Здесь немного хардкора. Можете просто это скопировать :) Что получилось?
ezgif.com-video-to-gif.gif

Если у вас есть идеи по улучшению или вопросы, пишите. Постараюсь ответить.


Comments 1


07.03.2018 08:19
0