Как стать автором
Обновить

Плавная анимация интерфейса (easing)

Время на прочтение5 мин
Количество просмотров20K
Приветствую всех читателей!
Хочу поделиться с вами простым, но эффектным способом анимировать юзер-интерфейс вашего приложения или сайта. В статье представлен готовый код на С++, который я использовал для анимирования 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. Визуально эти две картинки будут плавно заменять друг друга.

Вариантов использования анимаций очень много. Можно вращать элементы интерфейса, менять прозрачность, двигать по экрану, сжимать, растягивать и все это будет выглядеть плавно и приятно для глаза. Лично мне эти простые функции заметно экономят время на создание интерфейсов. Остается только не перестараться с анимациями и не запутать пользователя. Надеюсь вам пригодится материал из этой статьи. Если у вас есть предложения по новым типам анимаций — оставляйте комментарии, буду пополнять их список и обновлять статью.

Update

В качестве дополнительной иллюстрации создал вот такую страничку. Точно так же можно менять тип анимации, смотреть формулу и график.

Ссылки на похожие библиотеки
Tweener
Библиотека на Objective-C
спасибо nukie и Agent_Smith за ссылки

Теги:
Хабы:
Всего голосов 39: ↑34 и ↓5+29
Комментарии7

Публикации