Pull to refresh

Кастомизация переходных анимаций между Activity в ОС Android

Reading time 6 min
Views 18K
Добрый день! В этой статье мы будем рассматривать процесс создания кастомных анимаций переходов между Activity в Android при помощи ObjectAnimator и AnimatorSet. Всем, кому это интересно — добро пожаловать под кат.

PS: статья написана в основном для начинающих разработчиков.

Весь исходный код доступен на GitHub

Мы рассмотрим 2 вида переходных анимаций:
  • анимируем старую Activity на фоне новой
  • анимируем новую Activity на фоне старой

Для демонстрации были выбраны 2 типа анимации: одновременная анимация нескольких объектов (створки двери) и последовательная анимация нескольких объектов (сворачивание листа бумаги)

Анимация старой Activity



Итак, создадим 3 Activity: FirstActivity, SecondActivity и ThirdActivity.

Код FirstActivity:

first.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/click_layout"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:background="#00ff00"
        >
    <TextView
            android:layout_gravity="center"
            android:gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:textColor="#FFFFFF"
            android:textSize="40sp"
            android:text="First"
            />
</LinearLayout>

FirstActivity.java:
public class FirstActivity extends Activity {
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.first);

        LinearLayout click = (LinearLayout) findViewById(R.id.click_layout);
        click.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Bitmap bmp = getBitmap();

                ByteArrayOutputStream stream = new ByteArrayOutputStream();
                bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
                byte[] byteArray = stream.toByteArray();
                Intent intent = new Intent(FirstActivity.this, SecondActivity.class);
                intent.putExtra("picture", byteArray);
                startActivity(intent);
                overridePendingTransition(0,0);
            }
        });
    }

    private Bitmap getBitmap(){
        View root = getWindow().getDecorView().findViewById(android.R.id.content);
        root.setDrawingCacheEnabled(true);
        return root.getDrawingCache();
    }
}


Функция getBitmap возвращает Bitmap текущего окна. При клике по основному элементу Activity создаем новый Intent, и в качестве Extra задаем наш Bitmap, преобразованный в байтовый массив. После чего запускаем SecondActivity.

Код SecondActivity:

second.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="fill_parent"
              android:id="@+id/click_layout"
              android:background="#ff0000"
        >
    <TextView
            android:layout_gravity="center"
            android:gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="fill_parent"
            android:textColor="#FFFFFF"
            android:textSize="40sp"
            android:text="Second"
            />
    <LinearLayout android:orientation="horizontal"
                  android:weightSum="100"
            android:layout_width="fill_parent"
               android:layout_height="fill_parent">
        <ImageView android:layout_weight="50"
                   android:layout_width="0dip"
                   android:layout_height="fill_parent"
                android:id="@+id/left_image"/>
        <ImageView android:layout_weight="50"
                   android:layout_width="0dip"
                   android:layout_height="fill_parent"
                   android:id="@+id/right_image"/>
            </LinearLayout>
</FrameLayout>


В качестве главного элемента нашей Activity мы задаем FrameLayout, который содержит в себе 2 LinearLayout. В первом размещается весь необходимый контент(в нашем случае это TextView). Во втором находятся 2 ImageView, которые делят экран пополам. Онb необходимы нам для анимации.

Для анимации нам необходимо предпринять следующие шаги:
  1. Создать Bitmap из байтового массива, переданного из первыой Activity
  2. Разделить Bitmap на 2 части и загрузить их в соответствующий ImageView
  3. При помощи AnimatorSet одновременно воспроизвести анимацию поворота обоих ImageView


public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second);

        Bundle extras = getIntent().getExtras();
        byte[] byteArray = extras.getByteArray("picture");

        Bitmap bmp = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
        left = (ImageView) findViewById(R.id.left_image);
        right = (ImageView) findViewById(R.id.right_image);
        int centerWidth = bmp.getWidth()/2;
        Bitmap bmpLeft,bmpRight;
        bmpLeft = Bitmap.createBitmap(bmp,0,0,centerWidth,bmp.getHeight());
        bmpRight = Bitmap.createBitmap(bmp,centerWidth,0,bmp.getWidth() - centerWidth,bmp.getHeight());

        left.setImageBitmap(bmpLeft);
        right.setImageBitmap(bmpRight);

        ViewTreeObserver observer = left.getViewTreeObserver();
        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
            @Override
            public boolean onPreDraw() {
                left.getViewTreeObserver().removeOnPreDrawListener(this);
                startEnterAnimation();
                return true;  //To change body of implemented methods use File | Settings | File Templates.
            }
        });
    }


В функции onCreate мы получаем Bitmap из Extras, делим его на 2 Bitmap при помощи Bitmap.createBitmap() и устанавливаем полученные изображения в ImageView. После этого регистрируем Observer, который будет следить за onDraw событием ImageView и вызываться только один раз перед первой отрисовкой объекта. В нем мы запускаем анимацию открытия Activity.

private void startEnterAnimation() {
        left.setPivotY(left.getHeight()/2);
        left.setPivotX(0);
        right.setPivotY(left.getHeight()/2);
        right.setPivotX(right.getWidth());
        Animator leftAnim = ObjectAnimator.ofFloat(left, "rotationY", 0, 90);
        Animator rightAnim = ObjectAnimator.ofFloat(right, "rotationY", 0, -90);
        AnimatorSet set = new AnimatorSet();
        set.setDuration(500);
        set.playTogether(leftAnim, rightAnim);
        set.start();
    }


Непосредственно сама анимация описана в функции startEnterAnimation(). При помощи функций setPivotX() и setPivotY() мы устанавливаем точки, вокруг которых будут вращаться наши изображения. Далее мы задаем сами анимации вращения при помощи объекта ObjectAnimator.
ObjectAnimator — компонент системы, появившийся с Android 3.0. Он помогает анимировать какое-либо свойство любого объекта. В данном случае, мы анимируем свойство rotationX типа float объекта left.
Далее, мы создаем набор анимаций AnimatorSet, задаем длительность анимации в 500 мс и говорим ему, что будем одновременно проигрывать 2 анимации.
Анимация закрытия Activity задается аналогично. Для ее запуска мы переопределяем функцию Activity onBackPressed(). В ней мы запускаем анимацию закрытия, не забыв добавить listener с функцией finish().

Анимация новой Activity



Для того, чтобы анимировать новую Activity на фоне старой, нам не нужно ничего передавать в новую Activity. Нужно просто сделать фон ACtivity прозрачным. Для этого мы изменяем в файле AndroidManifest тему ThirdActivity на Transparent, и добавляем эту тему в styles.xml

<style name="Transparent">
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowBackground">@android:color/transparent</item>
    </style>


Сам процесс анимации будет заключаться в следующем:
  • при помощи наблюдателя OnPreDrawListener и функции getBitmap мы получаем Bitmap нашего Layout, после чего делаем его невидимым и запускаем анимацию открытия Activity
  • делим полученный Bitmap на 4 части, устанавливаем каждую часть в соответствующий Bitmap, после чего делаем невидимыми все изображения кроме начального
  • анимация открытия будет похожа на предыдущую, за исключением того, что после окончания анимации каждого куска изображения нам необходимо сделать видимым следующий кусок изображения
  • ну и в конце всей анимации мы делаем видимым Layout с контентов этой Activity, а изображения скрываем


Вся анимация запускается при помощи все того-же AnimatorSet, только задаются они при помощи функции playSequentially. Это означает, что все анимации будут запускаться последовательно друг за другом.

Анимация закрытия Activity делается аналогично, только в обратном порядке.

Заключение


Мы рассмотрели 2 способа создания переходной анимации между Activity. Весь код в статье адаптирован под версию системы выше 3.0, однако его легко можно адаптировать и под более ранние версии, начиная с 1.6, при помощи библиотеки NineOldAndroids от всем известного Jake Wharton'а. Единственное, о чем хотелось бы заострить внимание — это на установке относительных точек для поворота и увеличения. В этой библиотеке они устанавливаются при помощи объекта AnimatorProxy.

На этом все, если понравится эта статья — продолжу публикации на тему создания анимаций в ОС Android.
Tags:
Hubs:
+9
Comments 9
Comments Comments 9

Articles