Приветствую всех читателей!
Хочу поделиться с вами простым, но эффектным способом анимировать юзер-интерфейс вашего приложения или сайта. В статье представлен готовый код на С++, который я использовал для анимирования iOS и Android приложений, основанный на анимационных слайдерах.
Тут и тут вы уже видели примеры использования математических формул для создания плавной анимации (easing). Для наглядности я перевел часть кода на JavaScript и создал эту страничку, на которой вы можете посмотреть результат в действии. В этом простом примере используется только один слайдер для анимации и именно от него зависят все элементы интерфейса. Также вы можете выставить любую доступную анимацию для всех элементов интерфейса, посмотреть график и формулу, по которой происходит анимация.
Анимационные слайдеры
Анимационные слайдеры похожи на дорожки на микшерском пульте. По сути слайдер — это число от 0.0 до 1.0, где 0.0 — это начало анимации, а 1.0 — ее конец. Именно это число подставляется в математическую формулу. Зачем вообще нужны эти слайдеры? Нужны, потому что обычно в интерфейсе присутствует много элементов, каждому нужна анимация и их движение должно начинаться и заканчиваться одновременно. А вот тип анимации и координаты каждого элемента на экране могут быть разными.
Типы анимаций
Слайдер тоже можно двигать по-разному. Чаще всего элементы интерфейса при загрузке плавно появляются, а при переходе на другой экран плавно исчезают. Но для универсальности добавим анимацию по кругу, например у вас в приложении через весь экран проносятся искры или летают птицы, а так же анимацию вперед-назад — например для мигающих кнопок.
Функция анимации слайдера:
#define MAXSLIDES 5 //максимальное количество слайдеров
#define LOOP_ANIM 1 //анимация по кругу
#define FORWBACK_ANIM 2 //анимация вперед-назад
static float slide[MAXSLIDES]={0.0f}; //анимационные слайдеры, число от 0.0 до 1.0
static bool slideback[MAXSLIDES]={false}; //флаг означающий, что анимация проигрывается назад
static bool loopback[MAXSLIDES]={false}; //флаг для анимаций типа вперед-назад, означает что анимация проигрывается назад
static void ANIM(int n, float speed=1.0f, bool forw=true, char loop=0){
//n - какой слайдер двигаем
//speed - с какой скоростью
//forw - двигаем вперед или назад
//loop - тип анимации слайдера
if(loopback[n])forw=!forw; //если тип анимации вперед-назад и сейчас движемся назад то меняем начальное направление
slideback[n]=!forw; //запоминаем куда проигрываем анимацию
slide[n]+=fpsf*(forw?speed:-speed); //двигаем слайдер вперед или назад с заданной скоростью, где fpsf = 1.0/FPS
switch(loop){
case LOOP_ANIM: //анимация по кругу
if(slide[n]>1.0f)slide[n]-=1.0f;
else if(slide[n]<0.0f)slide[n]+=1.0f;
break;
case FORWBACK_ANIM: //анимация вперед-назад
if(slide[n]>1.0f){
slide[n]=2.0f-slide[n];
loopback[n]=!loopback[n];
}else if(slide[n]<0.0f){
slide[n]=-slide[n];
loopback[n]=!loopback[n];
}
break;
default: //обычная анимация до 0.0 или 1.0
if(slide[n]>1.0f)slide[n]=1.0f;
else if(slide[n]<0.0f)slide[n]=0.0f;
break;
}
}
Вывод элементов интерфейса
//типы анимаций
#define LINEAR 0
#define FADEIN 1
#define FADEOUT 2
#define BALL 3
#define SIN 4
#define INCENTER 5
#define FADEBOTH 6
#define FADEBOTH2 7
#define STEPS 8
#define LAMP 9
#define JUMPIN 10
#define JUMPOUT 11
static float A(int n, int type, float s, float e, int typeout=0, float e2=-1000.0f){
//где n - какой слайдер использовать
//type - тип анимации
//s и e - начальные и конечные значения, между которыми произойдет переход
//typeout - можно задать другой тип для обратной анимации (необязательное)
//e2 - конечное значение для обратной анимации (необязательное)
if(n<0){
//если номер слайдера отрицательный то переворачиваем анимацию
float tmp=s;
s=e;
e=tmp;
n=-n;
if(!slideback[n]){
if(typeout)type=typeout;
if(e2!=-1000.0f)e=e2;
}
}else{
//если анимация движется назад и указан другой типа для обратной анимации, используем его
if(slideback[n]){
if(typeout)type=typeout;
if(e2!=-1000.0f)s=e2;
}
}
float x=slide[n];
//различные формулы для анимаций
switch(type){
case FADEOUT:
x=x*x;
break;
case FADEIN:
x=1.0f-pow(x-1.0f, 2);
break;
case FADEBOTH:
x=pow(sinf(x*M_PI*0.5f), 2);
break;
case FADEBOTH2:
x=pow(sinf(x*M_PI*0.5f), 2);
x=pow(sinf(x*M_PI*0.5f), 2);
break;
case INCENTER:
x=tanf(x*2.0f-1.0f)/3.0f+0.5f;
break;
case BALL:{
float d=sinf((x-1.0f/12.0f)*M_PI*6.0f)*0.65f+0.65f;
x=d+sinf(x*M_PI*0.5f)*(1.0f-d);
}break;
case STEPS:
x=(int)(x*7.0f)/7.0f;
break;
case LAMP:
x=sinf(pow(expf(1.25f-x), 2)*4.0f);
if(x>0.0f)x=1.0f;
else x=0.0f;
break;
case JUMPOUT:
x=x*x*8.0f/3.0f-x*5.0f/3.0f;
break;
case JUMPIN:
x=x-1.0f;
x=-x*x*8.0f/3.0f-x*5.0f/3.0f+1.0f;
break;
}
//возвращаем значение между начальным и конечным в зависимости от типа анимации и текущего положения слайдера
return s+(e-s)*x;
}
Как все это использовать?
static void mainLoop(){
//в главном цикле двигаем все слайдеры
ANIM(1, 1.0f, showPage); //проигрываем слайдер №1 вперед если страницу надо показать, и назад если страница спрятана
ANIM(2, 2.0f, true, LOOP_ANIM); //проигрываем слайдер №2 в два раза быстрее, анимация будет крутиться по кругу
//выводим элементы интерфейса
glColor4f(1.0f, 1.0f, 1.0f, A(1, FADEIN, 0.0f, 1.0f));
DrawSomeImage(width/2, A(1, BALL, -150, height/2, FADEIN, height+200));
DrawOtherImage(A(2, INCENTER, -150, width+150), 100);
}
DrawSomeImage(width/2, A(1, BALL, -100, height/2, FADEIN, height+100));
Тут выводится некая картинка, допустим это логотип. По оси Х она выводится посередине экрана, а У у нас анимированый. Разберем каждый параметр:
1 — используем слайдер №1
BALL — анимация типа «мячик», логотип должен выпрыгнуть сверху экрана
-100 — это начальная координата по У т.е. вверху за пределами экрана
height/2 — значит посередине экрана по вертикали надо будет остановиться
Остальные параметры необязательные, но тогда при загрузке страницы логотип выпрыгнет сверху, что смотрится органично, а вот когда ему надо будет убраться обратно, анимация мячика будет смотреться не совсем естественно. Поэтому задаем тип обратной анимации, чтобы убирался он плавно с ускорением т.е. FADEIN.
Лично мне больше нравится, когда логотип улетает вниз а не обратно вверх, поэтому задаем куда ему потом улететь т.е. height+100.
Именно так выводится логотип в этом примере.
Так же мы плавно меняем прозрачность этой картинки:
glColor4f(1.0f, 1.0f, 1.0f, A(1, FADEIN, 0.0f, 1.0f));
Мы используем все тот же 1-й слайдер, чтобы синхронно менять и прозрачность, и координаты. Однако тип анимации ставим FADEIN (с ускорением) и меняем прозрачность от 0.0 до 1.0.
DrawOtherImage(A(2, INCENTER, -150, width+150), 100);
В этом случае картинка будет пролетать слева направо через весь экран, при этом будет плавно задерживаться посередине.
2 — используем слайдер №2, который проигрывается циклично и в два раза быстрее чем слайдер №1.
INCENTER — формула по которой картинка немного задержится посередине экрана
-150 — начальная координата по Х
width+150 — конечная координата по Х
Еще пример
glColor4f(1.0f, 1.0f, 1.0f, A(1, FADEBOTH, 0.0f, 1.0f));
DrawSomeImage1();
glColor4f(1.0f, 1.0f, 1.0f, A(-1, FADEBOTH, 0.0f, 1.0f));
DrawSomeImage2();
Как видно мы выводим две картинки с разной прозрачностью. Оба раза используем слайдер №1 и используем анимацию с плавным началом и концом. Отличается только знак в номере слайдера. -1 означает, что эта анимация перевернется и будет прятаться при слайдере равным 1.0, и выезжать при слайдере равным 0.0. Визуально эти две картинки будут плавно заменять друг друга.
Вариантов использования анимаций очень много. Можно вращать элементы интерфейса, менять прозрачность, двигать по экрану, сжимать, растягивать и все это будет выглядеть плавно и приятно для глаза. Лично мне эти простые функции заметно экономят время на создание интерфейсов. Остается только не перестараться с анимациями и не запутать пользователя. Надеюсь вам пригодится материал из этой статьи. Если у вас есть предложения по новым типам анимаций — оставляйте комментарии, буду пополнять их список и обновлять статью.
Автор: Apetrus