Страницы

Поиск по вопросам

Показаны сообщения с ярлыком animate. Показать все сообщения
Показаны сообщения с ярлыком animate. Показать все сообщения

суббота, 11 апреля 2020 г.

Анимация в Android Canvas для minSdkVersion=“8”

#java #android #анимация #animate

                    
В моей словесной игре типа "Эрудита" для Android отсутствует анимация - 

Например, при выборе в меню "Вернуть буквы" фишки просто удаляются с игрового поля
и потом (вдруг!) показываются внизу экрана. 

Тоже самое с "Помешать буквы" - фишки просто рисуются на новых позициях внизу экрана:



Конечно, это выглядит не очень наглядно и мне хотелось бы добавить простые анимации
для буквенных фишек - но при этом не терять совместимость с Android 2.2 - поэтому нельзя
например использовать ValueAnimator.

Кроме того, мне кажется, что можно обойтись без Runnable и создания дополнительных
thread-ов - потому что я уже успешно анимирую flinging игрового поля (когда пользователь
скроллит его энергичным движением пальца) в главном thread при помощи ScrollerCompat.

Вот класс буквенных фишек:

public class Tile {
    public static int sWidth;
    public static int sHeight;

    // этот прямоугольник содержит координаты и размер фишки
    private Rect mRect = new Rect();


Для анимации я решил добавить в этот класс конечные координаты и флаг, что движение
фишки еще не закончилось:

    private Point mTarget = new Point();
    public boolean moving = false;


И два метода для перемещения фишки - без и с анимацией:

    public void move(int x, int y) {
        mRect.left = x;
        mRect.top = y;
        mRect.right = x + sWidth;
        mRect.bottom = y + sHeight;
    }

    public void moveAnimated(int x, int y) {
        moving = true;
        mTarget.x = x;
        mTarget.y = y;
    }


Потом, в методе отрисовки моего главного custom View я вызываю:

public class GameView extends View {
    private static final int DELAY = 30;
    private ArrayList mBarTiles = new ArrayList();

    @Override
    protected void onDraw(Canvas canvas) {
        if (mGameBoard.isFlinging()) {
            postInvalidateDelayed(DELAY);
        } else {
            for (SmallTile tile: mBoardTiles) {
                if (tile.moving) {
                    postInvalidateDelayed(DELAY);
                    break;
                }
            }
        }

        // нарисовать (с учетом scroll-а) игровое поле с фишками
        mGameBoard.draw(canvas, mBoardTiles);

        // нарисовать до 7 фишек внизу экрана - с анимацией
        for (SmallTile tile: mBarTiles) 
            tile.draw(canvas);

        // нарисовать 1 фишку которую тащит пользователь
        mDraggedTile.draw(canvas);
    }


Как видите наверху, если одна из фишек еще в процессе движения (и ее флаг moving=true),
то вызывается onDraw еще раз через 30 миллисекунд. 

То же самое для анимации игрового поля - если оно еще движется (isFlinging() возвращает
true) - но это уже работает без проблем.

Теперь собственно мой вопрос:

В процедуре отрисовке фишки нужно учесть ее движение и выставить ее актуальные координаты:

public void draw(Canvas canvas) {
    if (moving) {
        int dx = mTarget.x - mRect.left;
        int dy = mTarget.y - mRect.top;

        // ВОТ ЗДЕСЬ Я ЗАСТРЯЛ - КАКИЕ КООРДИНАТЫ ВЫСТАВИТЬ?
        move(XXX, YYY); 

        // ЕСЛИ ФИШКА ДОЛЕТЕЛА - НУЖНО ВЫСТАВИТЬ moving=false
    }

    // нарисовать background фишки - желтый прямоугольник
    canvas.drawRect(mRect, mPaint);

    // нарисовать Bitmap с буквой и ее числовым значением
    canvas.save();
    canvas.translate(mRect.left, mRect.top);
    Drawable d = sLetters.get(mLetter);
    d.draw(canvas);
    canvas.restore();
}


К сожалению, я не знаю как правильно подсчитать координаты!

У меня есть актуальные координаты фишки: mRect.left и mRect.top

У меня есть координаты куда должна прилететь фишка: mTarget.x и mTarget.y

Пока moving==true, каждые 30 миллисекунд вызывается код отрисовки фишки, которому
нужно дать новые координаты - но как их подсчитать?
    


Ответы

Ответ 1



Тогда вам нужно выбрать время анимации, допустим 1 секунда = 1000 миллисек. Тогда поделив 1000 на 30 (fps по сути) получаем 33 - количество раз, которое мы вызовем метод draw для достижения цели. Получается, что наш путь dx и dy тоже нужно поделить на 33, тогда мы получим шаг, на который у нас будет сдвигаться фишка, при каждом вызове draw. Ну а дальше остается прибавлять этот шаг к текущим координатам и с ними вызывать метод move: move(mRect.left + (int) (dx / 33), mRect.top + (int) (dy / 33));

воскресенье, 29 марта 2020 г.

transition в canvas

#javascript #анимация #canvas #animate


Можно ли элементу в canvas добавить transition во время анимации? 






    
    Speedometer
    
    


Время загрузки 0.1 сек


Ответы

Ответ 1



CSS transition никак не применить к элементам на канве. Для получения плавной анимации в общем случае необходимо получать значения в зависимости от времени а не прибавлять каждый кадр фиксированные значения: Я изменил в Вашем примере одну функцию: let delay = 2; // задержка перед анимацией let duration = 3; // кол во секунд анимации роста значения let time = new Date().getTime(); // Время начала анимации let Engine = () => { // кол-во секунд с начала анимации let t = (new Date().getTime() - time) / 1000; val(+getValueFromTime(t).toFixed(1)); requestAnimFrame(Engine); }; // получить значение спидометра в зависимости от текущего времени function getValueFromTime(t) { if (t < delay) // если еще не прошло время задержки return 0 // вернем 0 t -= delay; // иначе вычитаем время задержки if (t < 0.95*duration) { // если прошло меньше 95 % времени анимации // переводим значение времени в значение при помощи функции сглаживания // EasingFunctions.easeOutCubic (значение на входе должно быть от 0 до 1) return 98 * EasingFunctions.easeOutCubic(t/duration); } // иначе это функция от синуса от этого значения return 98 + Math.sin(t * 55); } Функции сглаживания взяты отсюда, больше функций сглаживания тут
Время загрузки 0.1 сек


пятница, 14 февраля 2020 г.

Как сделать анимацию?

#css #css3 #анимация #css_animation #animate


Здравствуйте. Помогите, пожалуйста, сделать анимацию блоку такую, как это видно при
наведении на него. Например, через каждые три секунды, чтобы она повторялась дважды.
Раз-два...пауза 3сек... раз-два...пауза 3сек... и т.д. Спасибо!



#arrow,
#arrow_top {
	z-index: 10;
	position: fixed;
	overflow: hidden;
	margin: 0 auto;
	width: 64px;
	height: 64px;
}
#arrow {
	
	bottom: 0;
	left: 0;
	right: 0;
}
#arrow_top {
	top: 0;
	left: 0;
}
#arrow .arrow {
	-webkit-transition: all 0.3s ease-out;
	-moz-transition: all 0.3s ease-out;
	-ms-transition: all 0.3s ease-out;
	-o-transition: all 0.3s ease-out;
	transition: all 0.3s ease-out;
	position: relative;
	top: 0px;
}
#arrow:hover .arrow {
	top: 5px;
}
#arrow .arrow_down {
	width: 0px;
	height: 0px;
	background: red;
}
#arrow .arrow_down:after {
	position: absolute;
	content:"";
	top: 0px;
	left: 17px;
	width: 30px;
	height: 30px;
	background: transparent;
	border-right: 3px solid red;
	border-top: 3px solid red;
	-webkit-transform: rotate(135deg);
	-moz-transform: rotate(135deg);
	-ms-transform: rotate(135deg);
	-o-transform: rotate(135deg);
	transform: rotate(135deg);
}


Ответы

Ответ 1



Если совсем по простому, при помощи css анимации, как-то так: #arrow, #arrow_top { z-index: 10; position: fixed; overflow: hidden; margin: 0 auto; width: 64px; height: 64px; } #arrow { bottom: 0; left: 0; right: 0; animation: arrow 5s linear infinite; } @keyframes arrow { 0% { transform: translateY(0px); } 70% { transform: translateY(0px); } 73% { transform: translateY(5px); } 76% { transform: translateY(0px); } 79% { transform: translateY(5px); } 82% { transform: translateY(0px); } } 100% { transform: translateY(0px); } } #arrow_top { top: 0; left: 0; } #arrow .arrow { -webkit-transition: all 0.3s ease-out; -moz-transition: all 0.3s ease-out; -ms-transition: all 0.3s ease-out; -o-transition: all 0.3s ease-out; transition: all 0.3s ease-out; position: relative; top: 0px; } #arrow:hover .arrow { top: 5px; } #arrow .arrow_down { width: 0px; height: 0px; background: red; } #arrow .arrow_down:after { position: absolute; content:""; top: 0px; left: 17px; width: 30px; height: 30px; background: transparent; border-right: 3px solid red; border-top: 3px solid red; -webkit-transform: rotate(135deg); -moz-transform: rotate(135deg); -ms-transform: rotate(135deg); -o-transform: rotate(135deg); transform: rotate(135deg); }


понедельник, 10 февраля 2020 г.

Кастомный Switch с подписями

#jquery #css #button #animate #switch


Есть Switch с подписями, при клике на сам switch он должен переключаться, так же
переключение может происходить при клике на значение.





.switch input {
  display: none;
}
.switch {
  width: 45px;
  height: 15px;
  position: relative;
  display: inline-block;
}
.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: #00cc8c;
  transition: .3s;
}
.slider:before {
  content: "";
  height: 26px;
  width: 26px;
  position: absolute;
  left: 0;
  bottom: -5px;
  background-color: #008f62;
  transition: .3s;
}
input:checked + .slider {
  background-color: #f8ee7c;
}
input:focus + .slider {
  box-shadow: 0 0 1px #f8ee7c;
}
input:checked + .slider:before {
  transform: translateX(26px);
}
.slider.br {
  border-radius: 34px;
}
.slider.br:before {
  border-radius: 50%;
}





Возможно подключение jQuery. Сама изюминка вопроса в добавлении активных надписей
"Вкл/Выкл"

https://fiddle.jshell.net/w4t05476/
    


Ответы

Ответ 1



CSS: .selector-on { color: gray; cursor: pointer; } .selector-off { color: gray; cursor: pointer; } .switch input{ display:none; } .active-selector { color: #00cc8c; } .switch{ width: 45px; height: 15px; position: relative; display: inline-block; } .slider{ position: absolute; cursor: pointer; top: 0; right: 0; bottom: 0; left: 0; background-color: #00cc8c; transition: .3s; } .slider:before{ content: ""; height: 26px; width: 26px; position: absolute; left: 0; bottom: -5px; background-color: #008f62; transition: .3s; } input:checked + .slider{ background-color: #f8ee7c; } input:focus + .slider{ box-shadow: 0 0 1px #f8ee7c; } input:checked + .slider:before{ transform: translateX(26px); } .slider.br{ border-radius: 34px; } .slider.br:before{ border-radius: 50%; } HTML: On Off jQuery: function enableDisable(flag) { $('.my-checkbox').prop('checked', flag); $('.selector-on').toggleClass('active-selector', function() { return $(this).hasClass('active-selector') ? true : false; }); $('.selector-off').toggleClass('active-selector', function() { return $(this).hasClass('active-selector') ? true : false; }); } $('.selector-on').on('click', function(event) { return $(this).hasClass('active-selector') ? false : enableDisable(false); }); $('.selector-off').on('click', function(event) { return $(this).hasClass('active-selector') ? false : enableDisable(true); }); $('.my-checkbox').on('click', function() { if($(this).prop('checked')) { $('.selector-off').trigger('click'); } else { $('.selector-on').trigger('click'); } }); Пример

Ответ 2



$('input[type=checkbox]').before('On').after('Off'); $('input[type=checkbox]').onchange(function(){ $('#label_before').toggleClass('label_active'); $('#label_after').toggleClass('label_active'); }) Это если имеет смысл добавлять элементы через jQ. Я бы предпочел сразу все в html вставлять/через пхп генерить, не знаю, как там завязано у Вас все, но так не красиво, да и работает в идеале с одним свитчем. Код не тестировался, работоспособность не гарантируется. Гарантируется только смысл.

Ответ 3



.b-switch { margin: 15px 35px; font-family: 'segoe ui', sans-serif; font-size: 12px; } .b-switch input[type="checkbox"] { display: none; } .b-switch input[type="checkbox"] + label { width: 55px; height: 15px; position: relative; display: inline-block; background-color: #00cc8c; border-radius: 35px; transition: .3s; } .b-switch input[type="checkbox"] + label:before{ content: ""; position: absolute; left: -35px; top: 0; width: calc(100% + 70px); height: 100%; cursor: pointer; } .b-switch input[type="checkbox"] + label:after { content: ""; position: absolute; left: 0; bottom: -5px; height: 26px; width: 26px; background-color: #008f62; border-radius: 50%; cursor: pointer; transition: .3s; } .b-switch input[type="checkbox"] + label > span, .b-switch input[type="checkbox"] + label + span{ position: absolute; text-transform: uppercase; padding: 0 5px; color: #008F62; font-weight: 600; cursor: pointer; } .b-switch input[type="checkbox"] + label > span{ right: 100%; } .b-switch input[type="checkbox"]:checked + label{ background-color: #f8ee7c; } .b-switch input[type="checkbox"]:checked + label:after { left: calc(100% - 26px); } .b-switch input[type="checkbox"]:checked + label > span, .b-switch input[type="checkbox"] + label + span{ opacity: .5; pointer-events: none; } .b-switch input[type="checkbox"]:checked + label + span{ opacity: 1; }
Выкл
Выкл
Выкл


пятница, 24 января 2020 г.

Лучшая JS библиотека на базе PJAX с анимацией

#javascript #jquery #animate #pjax


Начал подробнее изучать вопрос построения сайта без перезагрузки страниц. Первым
делом поработал с чистым PJAX. Затем с его аналогом Barba. Второй мне показался более
удобным с точки зрения создания анимации. Однако, он имеет ряд недостатков. Нельзя
применить скрипт только к конкретным ссылкам, нельзя описать анимацию для разных ссылок.
Например, для навигации обычный fade-эффект, а для страницы новостей - смещение при
переходе влево и вправо (пример). Так же, было бы неплохо перезагружать некоторые скрипты
после отработки анимации перехода, например, стартовать слайдер на загружаемой странице
и т.д..

Сейчас планирую перейти к smoothState. Беглый осмотр показал, что она так же хорошо
подходит для создания анимации перехода по страницам и имеет множество гибких настроек.
Но, руки еще не дошли)

В общем-то вопрос следующий. Если у кого-то есть опыт в этой области и готовые примеры,
буду весьма признателен. Что хотелось бы увидеть в примере:


Плавный переход при переходе по основному меню.
Отличающийся стиль перехода на странице материала, например, смещение влево и вправо
(самый простой вариант был бы через использование библиотеки animate.css).
Перезагрузка выбранных скриптов и других тегов в шапке сайта.


Сделал чистый пример, для удобства размещения скриптов и тестирования результата.
Так он выглядит:




    
    
    
    Test project
    
    
    
    
    
    
    


 

    

Content

Если готовых решений ни у кого не окажется, свои результаты я естественно опубликую попозже.


Ответы

Ответ 1



Ваше исследование сподвигло на установку такой системы на свой сайт. Остановился на smoothState, на чем стоит заострить внимание: 1. Различные анимации на различные типы переходов. Достигается без проблем с помощью обработчика события onBefore: function($anchor, $container) $anchor - это jQuery объект с ссылкой, на которую нажали. Те. мы можем определить и класс ссылки, и адрес href и на их основе указать какую нам нужно анимацию. Например, добавив соответствующий класс на контейнер страницы, или сделав анимацию через jQuery animate. Но есть одно но. Это не сработает при переходе по истории. Будет какая-то стандартная анимация на кнопку Назад браузера 2. Смена контента вне контейнера страницы. В стандартном функционале нет, но в принципе достижимо правкой исходника. Например, мне нужно было менять класс тега от страницы к странице. Пришлось залезть в исходный код и поменять кое-что. Вдохновлялся этим. 3. Глюки при движении по истории, если вы тоже добавляете history.pushState() К сожалению, возникают ошибки, если вы тоже добавляете что-то в историю. Я добавляю якорные ссылки при переходе по главам статьи, что в дальнейшем вызывает ошибки у smoothState. Но, думаю, их тоже можно устранить.

воскресенье, 12 января 2020 г.

Подсчет шагов разных размеров - для более натуральной анимации Canvas

#java #android #анимация #animate


Здесь на русском StackOverflow я получил очень хороший совет и смог добавить анимацию
фишек в буквенную игру для Android.

Когда пользователь выбирает в меню "Вернуть буквы" и буква "A" должна прилететь в
позицию внизу экрана, показанную красной стрелкой:



Нынешний алгоритм работает так:


Текущие координаты фишки: mRect.top и mRect.left
Конечные координаты фишки: mTarget.x и mTarget.y
Пока на нем есть движущиеся фишки, экран мобильного приложения рисуется заново (для
этого снова и снова вызывается postInvalidate(30)).


Когда нужно анимированно переместить фишку, я вызываю следующий метод, который делит
предстоящий путь на 8 одинаковых шагов:

public void startAnimatedMove(float x, float y) {
    mTarget.x = x;
    mTarget.y = y;
    steps = 8;
    mStepX = (mTarget.x - mRect.left) / steps;
    mStepY = (mTarget.y - mRect.top) / steps;
}


Каждый раз, по истечении 30 миллисекунд, вызывается метод, сдвигающий фишку в нужном
направлении (причем последний шаг у меня особый - он помещает фишку точно на mTarget.x
и mTarget.y - чтобы избежать ошибки округления):

private void nextStep() {
    if (--steps == 0)
        moveTo(mTarget.x, mTarget.y);
    else 
        moveTo(mRect.left + mStepX, mRect.top + mStepY);
}


Этот алгоритм работает хорошо (и дает возможность анимировать фишки прямо из главного
thread-а приложения), но так как шаги mStepX и mStepY одинаковые, фишка движется неестественно.

Вопрос:

Как бы сделать шаги неодинаковыми? Они дожны быть большими в середине пути - и маленькими
в начале и конце (см. зеленый график наверху справа). Тогда фишка будет медленно начинать
и заканчивать движение - так называемый "easying in out".

То есть нужна функция (с использованием x*x или Math.sin()?) для каждой оси, которая
получала бы как input номер текушего шага, а выдавала как output дистанцию в пикселях
вдоль оси икс или игрек:

private void nextStep() {
    if (--steps == 0)
        moveTo(mTarget.x, mTarget.y);
    else 
        moveTo(mRect.left + calcStepX(steps), mRect.top + calcStepY(steps));
}


Обновление:

Нашел интересный сайт про Easing Equations by Robert Penner, но пока не разобрался,
как применить их к своему программному коду - особенно как рассчитать шаги, чтобы их
сумма попала в точку назначения...
    


Ответы

Ответ 1



как расчитать шаги, чтобы их сумма попала в точку назначения Все формулы на упомянутом сайте опираются на 4 параметра: t, b, c, d: t – текущее время (или номер кадра), b – начальное значение, c – (конечное значение - начальное значение), d – общая длительность анимации. Чтобы за 8 кадров некий параметр, напр., Y, изменился от 162 до 288, вы в понравившуюся формулу, напр., в экспонениальный out return c * ( -Math.pow( 2, -10 * t/d ) + 1 ) + b; подставляете b,c вашей анимации, d=7, а t=просчитываемый кадр (0..7) и получаете на выходе значение вашего параметра для этого кадра.

Ответ 2



Все гораздо проще. В android'е есть класс ValueAnimator Пользоваться примерно так: ValueAnimator va = ValueAnimator.ofInt(0, height); va.setDuration(300); va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { public void onAnimationUpdate(ValueAnimator animation) { Integer value = (Integer) animation.getAnimatedValue(); v.getLayoutParams().height = value.intValue(); v.requestLayout(); } }); Дефолтный интерполятор вычисляет значения именно так как вы хотите, т.е. начало и конец медленно, в середине быстрее. Вы естественно можете создать свой интерполятор. А если в интерполятор передать null, то будет линейно, как в вашем примере. ЗЫ Если надо анимировать два или больше независимых (т.е. когда dx через dy не посчитать) параметров можно воспользоваться AnimatorSet

суббота, 11 января 2020 г.

Наложение прозрачной текстовой маски на анимированный прогресс-бар (SVG)

#css #анимация #svg #animate #mask


У меня такая проблема, что не удается совместить прозрачную текстовую маску и прогресс-бар:
получается либо статичная прозрачная маска либо аниминированный прогресс-бар без надписи.

Необходимо сделать так, чтобы был фоновый круг и круг заполняющий (прогресс-бар)
с наложенной маской-текстом, через которую должен быть виден фон сайта, независимо
от количества слоёв (в данном случае кругов). 

Круг с прозрачной маской.



 
    
      
        
        HTML

      
    

    
    







И прогресс-бар.




      
       
           
              
         
        
       
      
      
     






Моя попытка их совместить.




      
          
            
            HTML
          
       
           
              
         
        
       
      
      
     




Подскажите, куда нужно вставлять маску, чтобы всё заработало.
    


Ответы

Ответ 1



Поскольку вы не используете maskContentUnits = objectBoundingBox , координаты фильтра рассчитываются глобально (относительно документа). И если вы сдвинули круг, неудивительно, что текст на маске теперь в него не попадает. Прошерcтите координаты элементов так, чтобы они попадали друг в друга, или попробуйте переделать фильтры под относительные координаты. HTML

Ответ 2



Html
JS PHP
Я так понял вопрос и немного доработал решение Crantisz . Для анимации кликайте по пунктам меню.

понедельник, 6 января 2020 г.

Новая строка в ListView при добавлении с БД, принимает значения ранее удаленной

#android #listview #animate #ontouchlistener


Есть список на основе ListView и SimpleCursorAdapter и SQLite.  

Пишу Swipe строки списка с помощью анимации. В ACTION_MOVE строка списка должна двигаться
влево за пальцем и при достижении определенного значения по оси X менять цвет и ставать
доступной для удаления. В этой зоне при ACTION_UP строка должна удалиться с БД по найденому
ID. Соответствующе в перерисованом ListView строка с удаляемой позицией исчезает из
списка на экране.  Все это работает, но возникает следующая проблемма:

При добавлении в БД новой строки она появляется в списке на экране в том состоянии
и в том положении по оси X которое было у ранее удаленной строки.

Предполагаю что, скорее всего, где-то ListView держит ранее использованные вьюхи
с теми характеристиками с которыми они были ранее удалены и заливает туда новые строки
с БД.

Пробовал применять после удаления: notifyDataSetChanged(), завершение анимации -
эффекта нет.

    public class MainActivity extends AppCompatActivity implements View.OnClickListener,
    View.OnTouchListener, LoaderManager.LoaderCallbacks {

DB db;
DBHelper dbHelper;
SimpleCursorAdapter scAdapter;
Context context;
String name;
String kind;

private View onTouchedItemView;

ListView lv_list;
Button btnEmpty, btnAdd, btnRead, btnClear;
TextView tvNameId, tvNameName, tvNameKind, tvNameCost;
EditText tvName, tvKind;

private int pointDownX;
private int pointDownY;
private long touchedItemID;

private int targetPoint;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    db = new DB(this);
    db.open();

    String[] from = new String[]{DB.KEY_ID, DB.KEY_NAME, DB.KEY_KIND, DB.KEY_COST};
    int[] to = new int[]{R.id.tvListId, R.id.tvListName, R.id.tvListKind, R.id.tvListCost};

    scAdapter = new SimpleCursorAdapter(this, R.layout.item, null, from, to, 0);

    lv_list = (ListView) findViewById(R.id.lv_list);
    lv_list.setAdapter(scAdapter);

    lv_list.setOnTouchListener(this);
    getSupportLoaderManager().initLoader(0, null, this);

    tvNameId = (TextView) findViewById(R.id.tvNameId);
    tvNameName = (TextView) findViewById(R.id.tvNameName);
    tvNameKind = (TextView) findViewById(R.id.tvNameKind);
    tvNameCost = (TextView) findViewById(R.id.tvNameCost);

    tvName = (EditText) findViewById(R.id.tvName);
    tvName.setOnClickListener(this);

    tvKind = (EditText) findViewById(R.id.tvKind);
    tvKind.setOnClickListener(this);

    btnAdd = (Button) findViewById(R.id.btnAdd);
    btnAdd.setOnClickListener(this);

    btnEmpty = (Button) findViewById(R.id.btnEmpty);
    btnEmpty.setOnClickListener(this);

    btnRead = (Button) findViewById(R.id.btnRead);
    btnRead.setOnClickListener(this);

    btnClear = (Button) findViewById(R.id.btnClear);
    btnClear.setOnClickListener(this);
}

@Override
public void onClick(View view) {
    name = tvName.getText().toString();
    kind = tvKind.getText().toString();

    switch (view.getId()) {
        case R.id.btnAdd:
            if (kind.isEmpty()) {
                kind = "1";
            }
            if (name.isEmpty()) {
                Toast.makeText(getApplicationContext(),
                        "Fill all fields", Toast.LENGTH_SHORT).show();
            } else {
                db.addRec(name, kind);
                getSupportLoaderManager().getLoader(0).forceLoad();
                tvName.setText("");
                tvKind.setText("");
            }
            break;

        case R.id.btnEmpty:
            tvName.setText("");
            tvKind.setText("");
            break;

        case R.id.btnRead:
            getSupportLoaderManager().getLoader(0).forceLoad();
            break;

        case R.id.btnClear:
            db.delAllRec();
            getSupportLoaderManager().getLoader(0).forceLoad();
            break;
    }
}

@Override
public Loader onCreateLoader(int id, Bundle bndl) {
    return new MyCursorLoader(this, db);
}

@Override
public void onLoadFinished(Loader loader, Cursor cursor) {
    scAdapter.swapCursor(cursor);
}

@Override
public void onLoaderReset(Loader loader) {
}


@Override
public boolean onTouch(View v, MotionEvent event) {

    int dispWidth = v.getWidth();
    int horizontalMinDistance = dispWidth / 4;
    int distFromRightBorder = dispWidth - pointDownX;
    int distToMeet = pointDownX / 10;

    switch (event.getAction()) {

        case MotionEvent.ACTION_DOWN:
            pointDownX = (int) event.getX();
            pointDownY = (int) event.getY();

            onTouchedItemView = lv_list.getChildAt(lv_list.pointToPosition(pointDownX,
pointDownY));
            touchedItemID = lv_list.pointToRowId(pointDownX, pointDownY);

            return true;

        case MotionEvent.ACTION_MOVE:
            int pointMovingX = (int) event.getX();
            int deltaX = pointDownX - pointMovingX;

            int distGone = distFromRightBorder / distToMeet * Math.abs(deltaX);

            if (deltaX < 0) {

                if (Math.abs(deltaX) < distToMeet) {
                    targetPoint = dispWidth - pointMovingX + pointDownX - distGone;

                } else {
                    targetPoint = pointMovingX;


                }
                anim();

            } else {

                if (Math.abs(deltaX) < distToMeet) {
                    targetPoint = pointMovingX - dispWidth + distFromRightBorder
- distGone;

                } else {
                    targetPoint = pointMovingX - dispWidth;

                    if (Math.abs(deltaX) > horizontalMinDistance && pointMovingX
< dispWidth / 8) {
                        onTouchedItemView.setBackgroundResource(R.color.deletemarker);

                    } else {
                        onTouchedItemView.setBackgroundResource(R.color.backgroundmain);
                    }
                }
                anim();
            }
            return true;

        case MotionEvent.ACTION_UP:
            float upX = (int) event.getRawX();

            if (upX < dispWidth / 8) {


                db.delRec(touchedItemID);


                getSupportLoaderManager().getLoader(0).forceLoad();

            } else {
                targetPoint = 0;
                anim();
            }
            return true;
    }
    return true;
}

public void anim() {
    onTouchedItemView.animate()
            .x(targetPoint)
            .setDuration(0)
            .start();
}

private static class MyCursorLoader extends CursorLoader {
    DB db;

    public MyCursorLoader(Context context, DB db) {
        super(context);
        this.db = db;
    }

    @Override
    public Cursor loadInBackground() {
        Cursor cursor = db.getAllData();
        return cursor;
    }
}

protected void onDestroy() {
    super.onDestroy();
    db.close();
}
}



    


Ответы

Ответ 1



Нашел решение. Применил обратную анимацию перед обновлением списка. onTouchedItemView.setBackgroundResource(R.color.backgroundmain); targetPoint = 0; anim(); И метод: clearDisappearingChildren(); Он удаляет анимацию на удаленном элементе списка. В коде выглядит так: case MotionEvent.ACTION_UP: float upX = (int) event.getRawX(); if (upX < dispWidth / 8) { db.delRec(touchedItemID); lv_list.clearDisappearingChildren(); getSupportLoaderManager().getLoader(0).forceLoad(); onTouchedItemView.setBackgroundResource(R.color.backgroundmain); targetPoint = 0; anim(); } else { targetPoint = 0; anim(); } return true;

вторник, 24 декабря 2019 г.

Как получить анимацию svg объекта, который связан через js с другим svg объектом, задавая анимацию только второму?

#javascript #анимация #svg #animate


Имеется две линии. Правый конец черной line1 можно передвигать мышкой. При этом координаты
конца зеленой линии line2 зависят от line1 через функцию moveline2.

Далее добавил анимацию первой линии по клику на красный круг. При этом line1 анимируется,
а функция moveline2, которая также пускается по этому событию срабатывает один раз
и все. Вторая линия остается без анимации, хотя функционально связана с анимированной
первой.

Понятно, что по мере того, как идет анимация должны генерироваться какие-то события
по которым можно пускать перерисовку второй линии, но как это сделать? 

Как заставить двигаться все объекты функционально связанные с единственным, на котором
висит анимация?
Использую библиотеку svg.js но, видимо, это не принципиально.
Документация по библиотеке https://svgdotjs.github.io



var paper = SVG('draw').size('100%', '100%');

var line1 = paper.line(0, 100, 300, 100).stroke({
  width: 5
}).style({
  cursor: 'pointer'
});

var line2 = paper.line(0, 200, 100, 200).stroke({
  width: 5
}).attr({
  stroke: 'green'
});

var circle = paper.circle(30).move(50, 50).fill('red').style({
  cursor: 'pointer'
});

//перемещение конца line1 примерно под курсор)
function move(event) {
  line1.attr({
    x2: event.pageX,
    y2: event.pageY
  })

  moveline2()

  //ловим событие перемещения мышки на холсте
  paper.mousemove(function(event) {
    line1.attr({
      x2: event.pageX,
      y2: event.pageY
    })

    moveline2()
  })

  paper.mouseup(function() {
    paper.mousemove(null)
  })
}

//функциональная зависимость координат второй линии от первой (любая)
function moveline2() {
  var x = line1.attr('x2');
  var y = line1.attr('y2');

  line2.attr({
    x2: x / 2,
    y2: (x + y) / 2
  })
}

//устанвка функциина нажатие ЛКМ над line1
line1.on('mousedown', move)

//анимация по клику 
circle.click(function() {
  line1.animate().attr({
    x2: 300,
    y2: 400
  })
  moveline2()
})
#draw {
  width: 400px;
  height: 400px;
  border: 2px solid silver
}




  Пример



  


Ответы

Ответ 1



Для выполнения собственной функции в течение анимации нужно использовать функции during/duringAll var paper = SVG('draw').size('100%', '100%'); var line1 = paper.line(0, 100, 300, 100).stroke({ width: 5 }).style({ cursor: 'pointer' }); var line2 = paper.line(0, 200, 100, 200).stroke({ width: 5 }).attr({ stroke: 'green' }); var circle = paper.circle(30).move(50, 50).fill('red').style({ cursor: 'pointer' }); //перемещение конца line1 примерно под курсор) function move(event) { line1.attr({ x2: event.pageX, y2: event.pageY }) moveline2() //ловим событие перемещения мышки на холсте paper.mousemove(function(event) { line1.attr({ x2: event.pageX, y2: event.pageY }) moveline2() }) paper.mouseup(function() { paper.mousemove(null) }) } //функциональная зависимость координат второй линии от первой (любая) function moveline2() { var x = line1.attr('x2'); var y = line1.attr('y2'); line2.attr({ x2: x / 2, y2: (x + y) / 2 }) } //устанвка функциина нажатие ЛКМ над line1 line1.on('mousedown', move) //анимация по клику circle.click(function() { line1.animate().attr({ x2: 300, y2: 400 }).during(function(pos, morph, eased, situation) { moveline2() }); }); #draw { width: 400px; height: 400px; border: 2px solid silver } Пример


пятница, 5 июля 2019 г.

Анимация в Android Canvas для minSdkVersion=“8”

В моей словесной игре типа "Эрудита" для Android отсутствует анимация -
Например, при выборе в меню "Вернуть буквы" фишки просто удаляются с игрового поля и потом (вдруг!) показываются внизу экрана.
Тоже самое с "Помешать буквы" - фишки просто рисуются на новых позициях внизу экрана:

Конечно, это выглядит не очень наглядно и мне хотелось бы добавить простые анимации для буквенных фишек - но при этом не терять совместимость с Android 2.2 - поэтому нельзя например использовать ValueAnimator
Кроме того, мне кажется, что можно обойтись без Runnable и создания дополнительных thread-ов - потому что я уже успешно анимирую flinging игрового поля (когда пользователь скроллит его энергичным движением пальца) в главном thread при помощи ScrollerCompat
Вот класс буквенных фишек:
public class Tile { public static int sWidth; public static int sHeight;
// этот прямоугольник содержит координаты и размер фишки private Rect mRect = new Rect();
Для анимации я решил добавить в этот класс конечные координаты и флаг, что движение фишки еще не закончилось:
private Point mTarget = new Point(); public boolean moving = false;
И два метода для перемещения фишки - без и с анимацией:
public void move(int x, int y) { mRect.left = x; mRect.top = y; mRect.right = x + sWidth; mRect.bottom = y + sHeight; }
public void moveAnimated(int x, int y) { moving = true; mTarget.x = x; mTarget.y = y; }
Потом, в методе отрисовки моего главного custom View я вызываю:
public class GameView extends View { private static final int DELAY = 30; private ArrayList mBarTiles = new ArrayList();
@Override protected void onDraw(Canvas canvas) { if (mGameBoard.isFlinging()) { postInvalidateDelayed(DELAY); } else { for (SmallTile tile: mBoardTiles) { if (tile.moving) { postInvalidateDelayed(DELAY); break; } } }
// нарисовать (с учетом scroll-а) игровое поле с фишками mGameBoard.draw(canvas, mBoardTiles);
// нарисовать до 7 фишек внизу экрана - с анимацией for (SmallTile tile: mBarTiles) tile.draw(canvas);
// нарисовать 1 фишку которую тащит пользователь mDraggedTile.draw(canvas); }
Как видите наверху, если одна из фишек еще в процессе движения (и ее флаг moving=true), то вызывается onDraw еще раз через 30 миллисекунд.
То же самое для анимации игрового поля - если оно еще движется (isFlinging() возвращает true) - но это уже работает без проблем.
Теперь собственно мой вопрос
В процедуре отрисовке фишки нужно учесть ее движение и выставить ее актуальные координаты:
public void draw(Canvas canvas) { if (moving) { int dx = mTarget.x - mRect.left; int dy = mTarget.y - mRect.top;
// ВОТ ЗДЕСЬ Я ЗАСТРЯЛ - КАКИЕ КООРДИНАТЫ ВЫСТАВИТЬ? move(XXX, YYY);
// ЕСЛИ ФИШКА ДОЛЕТЕЛА - НУЖНО ВЫСТАВИТЬ moving=false }
// нарисовать background фишки - желтый прямоугольник canvas.drawRect(mRect, mPaint);
// нарисовать Bitmap с буквой и ее числовым значением canvas.save(); canvas.translate(mRect.left, mRect.top); Drawable d = sLetters.get(mLetter); d.draw(canvas); canvas.restore(); }
К сожалению, я не знаю как правильно подсчитать координаты!
У меня есть актуальные координаты фишки: mRect.left и mRect.top
У меня есть координаты куда должна прилететь фишка: mTarget.x и mTarget.y
Пока moving==true, каждые 30 миллисекунд вызывается код отрисовки фишки, которому нужно дать новые координаты - но как их подсчитать?


Ответ

Тогда вам нужно выбрать время анимации, допустим 1 секунда = 1000 миллисек. Тогда поделив 1000 на 30 (fps по сути) получаем 33 - количество раз, которое мы вызовем метод draw для достижения цели. Получается, что наш путь dx и dy тоже нужно поделить на 33, тогда мы получим шаг, на который у нас будет сдвигаться фишка, при каждом вызове draw. Ну а дальше остается прибавлять этот шаг к текущим координатам и с ними вызывать метод move
move(mRect.left + (int) (dx / 33), mRect.top + (int) (dy / 33));

пятница, 14 июня 2019 г.

бесконечная анимация с задержками в SMIL

Добрый день уважаемые, подскажите пожалуйста, как в SMIL реализовать задержки в бесконечно повторяющейся анимации? Приведу пример
#work{ border: 1px solid #000; } #obgect{ animation: moveObject 4s linear infinite; } @keyframes moveObject{ 0%,100%{ transform: translateX(0); } 50%{ transform: translateX(100px); } }
На первом проходе всё хорошо. - После первой секунды шарик увеличиваеться, на 3-й секунде он приобретает свой изначальный размер, а дальше идет полное рассогласование. Мне необходимо понять как сделать так что б шарик менял свои размеры только с 2 по 3сек в бесконечном цикле. (CSS анимацию не трогаем) Собственно и вопрос - как реализовать повторяющиеся задержки в анимации бесконечного цыкла ?


Ответ

SMIL - это чистая декларативка. Нет переменных, функций и т.д Поэтому некуда запоминать текущее состояние в бесконечном цикле. Но вы хотели получить анимацию на чистом SMIL, поэтому я использовал возможность сделать цепочку последовательных анимаций и последняя анимация переходит к исполнению первой анимации begin="0s;an6.end", а в качестве пауз я поставил заглушки-анимации, которые ничего не делают, но участвуют в цепочке анимаций. Это строки с атрибутом - values="10;10"
Ниже тайминг последовательных анимаций. Зелёный прямоугольник - это горизонтальное перемещение шарика туда и обратно, заданное в стилях CSS
Увеличил немного длину и время пробега шарика для наглядности.

an1, an3, an4, an6 - идентификаторы заглушек- анимаций, которые обеспечивают паузы между анимациями an2 и an5 увеличения и уменьшения размеров шарика.

суббота, 9 марта 2019 г.

Подсчет шагов разных размеров - для более натуральной анимации Canvas

Здесь на русском StackOverflow я получил очень хороший совет и смог добавить анимацию фишек в буквенную игру для Android.
Когда пользователь выбирает в меню "Вернуть буквы" и буква "A" должна прилететь в позицию внизу экрана, показанную красной стрелкой:

Нынешний алгоритм работает так:
Текущие координаты фишки: mRect.top и mRect.left Конечные координаты фишки: mTarget.x и mTarget.y Пока на нем есть движущиеся фишки, экран мобильного приложения рисуется заново (для этого снова и снова вызывается postInvalidate(30)).
Когда нужно анимированно переместить фишку, я вызываю следующий метод, который делит предстоящий путь на 8 одинаковых шагов:
public void startAnimatedMove(float x, float y) { mTarget.x = x; mTarget.y = y; steps = 8; mStepX = (mTarget.x - mRect.left) / steps; mStepY = (mTarget.y - mRect.top) / steps; }
Каждый раз, по истечении 30 миллисекунд, вызывается метод, сдвигающий фишку в нужном направлении (причем последний шаг у меня особый - он помещает фишку точно на mTarget.x и mTarget.y - чтобы избежать ошибки округления):
private void nextStep() { if (--steps == 0) moveTo(mTarget.x, mTarget.y); else moveTo(mRect.left + mStepX, mRect.top + mStepY); }
Этот алгоритм работает хорошо (и дает возможность анимировать фишки прямо из главного thread-а приложения), но так как шаги mStepX и mStepY одинаковые, фишка движется неестественно.
Вопрос:
Как бы сделать шаги неодинаковыми? Они дожны быть большими в середине пути - и маленькими в начале и конце (см. зеленый график наверху справа). Тогда фишка будет медленно начинать и заканчивать движение - так называемый "easying in out".
То есть нужна функция (с использованием x*x или Math.sin()?) для каждой оси, которая получала бы как input номер текушего шага, а выдавала как output дистанцию в пикселях вдоль оси икс или игрек:
private void nextStep() { if (--steps == 0) moveTo(mTarget.x, mTarget.y); else moveTo(mRect.left + calcStepX(steps), mRect.top + calcStepY(steps)); }
Обновление:
Нашел интересный сайт про Easing Equations by Robert Penner, но пока не разобрался, как применить их к своему программному коду - особенно как рассчитать шаги, чтобы их сумма попала в точку назначения...


Ответ

как расчитать шаги, чтобы их сумма попала в точку назначения
Все формулы на упомянутом сайте опираются на 4 параметра: t, b, c, d
t – текущее время (или номер кадра), b – начальное значение, c – (конечное значение - начальное значение), d – общая длительность анимации.
Чтобы за 8 кадров некий параметр, напр., Y, изменился от 162 до 288, вы в понравившуюся формулу, напр., в экспонениальный out
return c * ( -Math.pow( 2, -10 * t/d ) + 1 ) + b;
подставляете b,c вашей анимации, d=7, а t=просчитываемый кадр (0..7) и получаете на выходе значение вашего параметра для этого кадра.

вторник, 12 февраля 2019 г.

Новая строка в ListView при добавлении с БД, принимает значения ранее удаленной

Есть список на основе ListView и SimpleCursorAdapter и SQLite.
Пишу Swipe строки списка с помощью анимации. В ACTION_MOVE строка списка должна двигаться влево за пальцем и при достижении определенного значения по оси X менять цвет и ставать доступной для удаления. В этой зоне при ACTION_UP строка должна удалиться с БД по найденому ID. Соответствующе в перерисованом ListView строка с удаляемой позицией исчезает из списка на экране. Все это работает, но возникает следующая проблемма:
При добавлении в БД новой строки она появляется в списке на экране в том состоянии и в том положении по оси X которое было у ранее удаленной строки.
Предполагаю что, скорее всего, где-то ListView держит ранее использованные вьюхи с теми характеристиками с которыми они были ранее удалены и заливает туда новые строки с БД.
Пробовал применять после удаления: notifyDataSetChanged(), завершение анимации - эффекта нет.
public class MainActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener, LoaderManager.LoaderCallbacks {
DB db; DBHelper dbHelper; SimpleCursorAdapter scAdapter; Context context; String name; String kind;
private View onTouchedItemView;
ListView lv_list; Button btnEmpty, btnAdd, btnRead, btnClear; TextView tvNameId, tvNameName, tvNameKind, tvNameCost; EditText tvName, tvKind;
private int pointDownX; private int pointDownY; private long touchedItemID;
private int targetPoint;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
db = new DB(this); db.open();
String[] from = new String[]{DB.KEY_ID, DB.KEY_NAME, DB.KEY_KIND, DB.KEY_COST}; int[] to = new int[]{R.id.tvListId, R.id.tvListName, R.id.tvListKind, R.id.tvListCost};
scAdapter = new SimpleCursorAdapter(this, R.layout.item, null, from, to, 0);
lv_list = (ListView) findViewById(R.id.lv_list); lv_list.setAdapter(scAdapter);
lv_list.setOnTouchListener(this); getSupportLoaderManager().initLoader(0, null, this);
tvNameId = (TextView) findViewById(R.id.tvNameId); tvNameName = (TextView) findViewById(R.id.tvNameName); tvNameKind = (TextView) findViewById(R.id.tvNameKind); tvNameCost = (TextView) findViewById(R.id.tvNameCost);
tvName = (EditText) findViewById(R.id.tvName); tvName.setOnClickListener(this);
tvKind = (EditText) findViewById(R.id.tvKind); tvKind.setOnClickListener(this);
btnAdd = (Button) findViewById(R.id.btnAdd); btnAdd.setOnClickListener(this);
btnEmpty = (Button) findViewById(R.id.btnEmpty); btnEmpty.setOnClickListener(this);
btnRead = (Button) findViewById(R.id.btnRead); btnRead.setOnClickListener(this);
btnClear = (Button) findViewById(R.id.btnClear); btnClear.setOnClickListener(this); }
@Override public void onClick(View view) { name = tvName.getText().toString(); kind = tvKind.getText().toString();
switch (view.getId()) { case R.id.btnAdd: if (kind.isEmpty()) { kind = "1"; } if (name.isEmpty()) { Toast.makeText(getApplicationContext(), "Fill all fields", Toast.LENGTH_SHORT).show(); } else { db.addRec(name, kind); getSupportLoaderManager().getLoader(0).forceLoad(); tvName.setText(""); tvKind.setText(""); } break;
case R.id.btnEmpty: tvName.setText(""); tvKind.setText(""); break;
case R.id.btnRead: getSupportLoaderManager().getLoader(0).forceLoad(); break;
case R.id.btnClear: db.delAllRec(); getSupportLoaderManager().getLoader(0).forceLoad(); break; } }
@Override public Loader onCreateLoader(int id, Bundle bndl) { return new MyCursorLoader(this, db); }
@Override public void onLoadFinished(Loader loader, Cursor cursor) { scAdapter.swapCursor(cursor); }
@Override public void onLoaderReset(Loader loader) { }
@Override public boolean onTouch(View v, MotionEvent event) {
int dispWidth = v.getWidth(); int horizontalMinDistance = dispWidth / 4; int distFromRightBorder = dispWidth - pointDownX; int distToMeet = pointDownX / 10;
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: pointDownX = (int) event.getX(); pointDownY = (int) event.getY();
onTouchedItemView = lv_list.getChildAt(lv_list.pointToPosition(pointDownX, pointDownY)); touchedItemID = lv_list.pointToRowId(pointDownX, pointDownY);
return true;
case MotionEvent.ACTION_MOVE: int pointMovingX = (int) event.getX(); int deltaX = pointDownX - pointMovingX;
int distGone = distFromRightBorder / distToMeet * Math.abs(deltaX);
if (deltaX < 0) {
if (Math.abs(deltaX) < distToMeet) { targetPoint = dispWidth - pointMovingX + pointDownX - distGone;
} else { targetPoint = pointMovingX;
} anim();
} else {
if (Math.abs(deltaX) < distToMeet) { targetPoint = pointMovingX - dispWidth + distFromRightBorder - distGone;
} else { targetPoint = pointMovingX - dispWidth;
if (Math.abs(deltaX) > horizontalMinDistance && pointMovingX < dispWidth / 8) { onTouchedItemView.setBackgroundResource(R.color.deletemarker);
} else { onTouchedItemView.setBackgroundResource(R.color.backgroundmain); } } anim(); } return true;
case MotionEvent.ACTION_UP: float upX = (int) event.getRawX();
if (upX < dispWidth / 8) {
db.delRec(touchedItemID);
getSupportLoaderManager().getLoader(0).forceLoad();
} else { targetPoint = 0; anim(); } return true; } return true; }
public void anim() { onTouchedItemView.animate() .x(targetPoint) .setDuration(0) .start(); }
private static class MyCursorLoader extends CursorLoader { DB db;
public MyCursorLoader(Context context, DB db) { super(context); this.db = db; }
@Override public Cursor loadInBackground() { Cursor cursor = db.getAllData(); return cursor; } }
protected void onDestroy() { super.onDestroy(); db.close(); } }


Ответ

Нашел решение.
Применил обратную анимацию перед обновлением списка.
onTouchedItemView.setBackgroundResource(R.color.backgroundmain); targetPoint = 0; anim();
И метод:
clearDisappearingChildren();
Он удаляет анимацию на удаленном элементе списка.
В коде выглядит так:
case MotionEvent.ACTION_UP:
float upX = (int) event.getRawX();
if (upX < dispWidth / 8) {
db.delRec(touchedItemID);
lv_list.clearDisappearingChildren();
getSupportLoaderManager().getLoader(0).forceLoad(); onTouchedItemView.setBackgroundResource(R.color.backgroundmain); targetPoint = 0; anim();
} else {
targetPoint = 0; anim();
} return true;

понедельник, 26 ноября 2018 г.

Как получить анимацию svg объекта, который связан через js с другим svg объектом, задавая анимацию только второму?

Имеется две линии. Правый конец черной line1 можно передвигать мышкой. При этом координаты конца зеленой линии line2 зависят от line1 через функцию moveline2.
Далее добавил анимацию первой линии по клику на красный круг. При этом line1 анимируется, а функция moveline2, которая также пускается по этому событию срабатывает один раз и все. Вторая линия остается без анимации, хотя функционально связана с анимированной первой.
Понятно, что по мере того, как идет анимация должны генерироваться какие-то события по которым можно пускать перерисовку второй линии, но как это сделать?
Как заставить двигаться все объекты функционально связанные с единственным, на котором висит анимация? Использую библиотеку svg.js но, видимо, это не принципиально. Документация по библиотеке https://svgdotjs.github.io
var paper = SVG('draw').size('100%', '100%'); var line1 = paper.line(0, 100, 300, 100).stroke({ width: 5 }).style({ cursor: 'pointer' }); var line2 = paper.line(0, 200, 100, 200).stroke({ width: 5 }).attr({ stroke: 'green' }); var circle = paper.circle(30).move(50, 50).fill('red').style({ cursor: 'pointer' }); //перемещение конца line1 примерно под курсор) function move(event) { line1.attr({ x2: event.pageX, y2: event.pageY }) moveline2() //ловим событие перемещения мышки на холсте paper.mousemove(function(event) { line1.attr({ x2: event.pageX, y2: event.pageY }) moveline2() }) paper.mouseup(function() { paper.mousemove(null) }) } //функциональная зависимость координат второй линии от первой (любая) function moveline2() { var x = line1.attr('x2'); var y = line1.attr('y2'); line2.attr({ x2: x / 2, y2: (x + y) / 2 }) } //устанвка функциина нажатие ЛКМ над line1 line1.on('mousedown', move) //анимация по клику circle.click(function() { line1.animate().attr({ x2: 300, y2: 400 }) moveline2() }) #draw { width: 400px; height: 400px; border: 2px solid silver } Пример



Ответ

Для выполнения собственной функции в течение анимации нужно использовать функции during/duringAll
var paper = SVG('draw').size('100%', '100%'); var line1 = paper.line(0, 100, 300, 100).stroke({ width: 5 }).style({ cursor: 'pointer' }); var line2 = paper.line(0, 200, 100, 200).stroke({ width: 5 }).attr({ stroke: 'green' }); var circle = paper.circle(30).move(50, 50).fill('red').style({ cursor: 'pointer' }); //перемещение конца line1 примерно под курсор) function move(event) { line1.attr({ x2: event.pageX, y2: event.pageY }) moveline2() //ловим событие перемещения мышки на холсте paper.mousemove(function(event) { line1.attr({ x2: event.pageX, y2: event.pageY }) moveline2() }) paper.mouseup(function() { paper.mousemove(null) }) } //функциональная зависимость координат второй линии от первой (любая) function moveline2() { var x = line1.attr('x2'); var y = line1.attr('y2'); line2.attr({ x2: x / 2, y2: (x + y) / 2 }) } //устанвка функциина нажатие ЛКМ над line1 line1.on('mousedown', move) //анимация по клику circle.click(function() { line1.animate().attr({ x2: 300, y2: 400 }).during(function(pos, morph, eased, situation) { moveline2() }); }); #draw { width: 400px; height: 400px; border: 2px solid silver } Пример