Освой программирование играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

AnimationDrawable (<animation-list>)

Пример с анимацией
Изменяем прозрачность
Анимация при загрузке приложения
Анимация градиентного фона

Кадровая (фреймовая) анимация — традиционная анимация при помощи быстрой смены последовательных изображений, как на киноплёнке. Данный вид анимации использует XML-файлы в каталоге res/anim/.

XML-файл состоит из корневого элемента <animation-list> и дочерних узлов <item>, каждый из которых определяет кадр, имеющий две составляющие:

  • графический ресурс для кадра
  • продолжительность кадра

<animation-list xmlns:android="http://schemas.android.com/apk/res/android" 
    android:oneshot="true"> 
    <item android:drawable="@drawable/filel" android:duration="200" 
    <item android:drawable="@drawable/file2" android:duration="200" 
    <item android:drawable="@drawable/file3" android:duration="200" 
</animation-list> 

Данная анимация будет выполняться только для трёх кадров. При установке атрибута android:oneshot в true анимация повторится только один раз и после остановки будет содержать последний кадр. Если же атрибут установить в false, то анимация будет циклической. Данный XML-файл, сохраненный в каталоге res/anim проекта, можно добавить как фоновое изображение для компонента и затем запустить анимацию.

Пример с анимацией

Создадим новый проект и добавим на форму две кнопки для управления анимацией.


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/buttonStart"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Пуск" />

        <Button
            android:id="@+id/buttonStop"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="Стоп" />
    </LinearLayout>

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

Для анимации подготовим заранее отобранные файлы, которые необходимо поместить в каталог res/drawable. Наша анимация будет состоять из восьми кадров. Время показа каждого кадра установим в 250 миллисекунд. Запишем наши настройки в XML-файл catanimation.xml в каталоге res/drawable.


<animation-list 
	xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item android:drawable="@drawable/fitness1" android:duration="250"/>
    <item android:drawable="@drawable/fitness2" android:duration="250"/>
    <item android:drawable="@drawable/fitness3" android:duration="250"/>
    <item android:drawable="@drawable/fitness4" android:duration="250"/>
    <item android:drawable="@drawable/fitness5" android:duration="250"/>
    <item android:drawable="@drawable/fitness6" android:duration="250"/>
    <item android:drawable="@drawable/fitness7" android:duration="250"/>
    <item android:drawable="@drawable/fitness8" android:duration="250"/>
</animation-list>

Получить объект AnimationDrawable в коде программы можно так:

 
ImageView image = findViewById(R.id.image); 
image.setBackgroundResource(R.drawable.catanimation); 
AnimationDrawable animation (AnimationDrawable)image.getBackground(); 

Управлять объектом AnimationDrawable можно через методы start() и stop().


package ru.alexanderklimov.testapplication;

import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends ActionBarActivity {

    private AnimationDrawable mAnimationDrawable;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        ImageView imageView = (ImageView) findViewById(R.id.imageView);
        imageView.setBackgroundResource(R.drawable.catanimation);

        mAnimationDrawable = (AnimationDrawable) imageView.getBackground();

        final Button btnStart = findViewById(R.id.buttonStart);
        btnStart.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                mAnimationDrawable.start();
            }
        });

        final Button btnStop = findViewById(R.id.buttonStop);
        btnStop.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                mAnimationDrawable.stop();
            }
        });
    }
}    

Запустите приложение и посмотрите на анимацию. Надеюсь, вид кота, делающего упражнения, заставит вас с понедельника начать делать зарядку.

Создание анимации в коде программы

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

В основном классе определим два внутренних метода startFrameAnimation() и stopFrameAnimation(), которые будем вызывать при нажатии кнопок "Пуск" и "Стоп".

В методе startFrameAnimation() реализуем создание анимации. Надо получить кадры анимации в виде набора объектов Drawable, загрузив изображения из ресурсов. Для каждого кадра создается отдельный объект Drawable:


BitmapDrawable frame1 =
    (BitmapDrawable)getResources().getDrawable(R.drawable.fitness1); 
BitmapDrawable frame2 =
    (BitmapDrawable)getResources().getDrawable(R.drawable.fitness2); 
BitmapDrawable frame3 
    (BitmapDrawable)getResources().getDrawable(R.drawable.fitness3); 
// ... и так далее

Созданные объекты BitmapDrawable необходимо добавить в объект AnimationDrawable методом addFrame(), который принимает два параметра: кадр анимации (объект Drawable) и продолжительность показа в миллисекундах.


AnimationDrawable mAnimation = new AnimationDrawable(); 
// устанавливаем циклическое повторение анимации 
mAnimation.setOneShot(false); 
mAnimation.addFrame(framel, 250); 
mAnimation.addFrame(frame2, 250); 
mAnimation.addFrame(frame3, 250); 

Полный листинг будет следующим:


package ru.alexanderklimov.animation;

import android.graphics.drawable.AnimationDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;

public class MainActivity extends ActionBarActivity {

    private AnimationDrawable mAnimationDrawable = null;
    private final static int DURATION = 250;
    private ImageView mImageView;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        mImageView = findViewById(R.id.imageView);

        final Button btnStart = findViewById(R.id.buttonStart);
        btnStart.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                startFrameAnimation();
            }
        });

        final Button btnStop = findViewById(R.id.buttonStop);
        btnStop.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                stopFrameAnimation();
            }
        });
    }

    private void startFrameAnimation() {
        BitmapDrawable frame1 = (BitmapDrawable) getResources().getDrawable(
                R.drawable.fitness1);
        BitmapDrawable frame2 = (BitmapDrawable) getResources().getDrawable(
                R.drawable.fitness2);
        BitmapDrawable frame3 = (BitmapDrawable) getResources().getDrawable(
                R.drawable.fitness3);
        BitmapDrawable frame4 = (BitmapDrawable) getResources().getDrawable(
                R.drawable.fitness4);
        BitmapDrawable frame5 = (BitmapDrawable) getResources().getDrawable(
                R.drawable.fitness5);
        BitmapDrawable frame6 = (BitmapDrawable) getResources().getDrawable(
                R.drawable.fitness6);
        BitmapDrawable frame7 = (BitmapDrawable) getResources().getDrawable(
                R.drawable.fitness7);
        BitmapDrawable frame8 = (BitmapDrawable) getResources().getDrawable(
                R.drawable.fitness8);

        mAnimationDrawable = new AnimationDrawable();

        mAnimationDrawable.setOneShot(false);
        mAnimationDrawable.addFrame(frame1, DURATION);
        mAnimationDrawable.addFrame(frame2, DURATION);
        mAnimationDrawable.addFrame(frame3, DURATION);
        mAnimationDrawable.addFrame(frame4, DURATION);
        mAnimationDrawable.addFrame(frame5, DURATION);
        mAnimationDrawable.addFrame(frame6, DURATION);
        mAnimationDrawable.addFrame(frame7, DURATION);
        mAnimationDrawable.addFrame(frame8, DURATION);

        mImageView.setBackground(mAnimationDrawable);

        if (!mAnimationDrawable.isRunning()) {
            mAnimationDrawable.setVisible(true, true);
            mAnimationDrawable.start();
        }
    }

    private void stopFrameAnimation() {
        if (mAnimationDrawable.isRunning()) {
            mAnimationDrawable.stop();
            mAnimationDrawable.setVisible(false, false);
        }
    }
}

В методах я также использовал проверку на работающую анимацию (isRunning()), хотя и без этой проверки всё работало. Но мало ли что.

Результат будет тот же самый (теоретически). У меня в эмуляторе почему-то работали только первые три кадра. На форумах многие пишут, что у них выводится только первый кадр. Отсюда можно сделать вывод, что не нужно использовать программную анимацию, а использовать анимацию через XML. Статья писалась в 2012 году, возможно сейчас на эмуляторе таких проблем нет.

Изменяем прозрачность

Класс AnimationDrawable имеет метод setAlpha(), позволяющий изменять прозрачность кадров от 255 до 0. Модифицируем предыдущий пример. Добавим на форму метку и ползунок:


<TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Set Alpha" />

<SeekBar
    android:id="@+id/seekBarAlpha"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:max="255"
    android:progress="255" />

В коде программы добавим слушатель изменения ползунка в методе onCreate():


SeekBar alphaSeekBar = findViewById(R.id.seekBarAlpha);

alphaSeekBar
        .setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar,
                                          int progress, boolean fromUser) {
                mAnimationDrawable.setAlpha(progress);
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                // TODO Auto-generated method stub
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                // TODO Auto-generated method stub
            }
        });

FrameAnimation

Анимация при загрузке приложения

В документации по кадровой анимации имеется предупреждение, что при запуске программы анимация не запустится, так как не все необходимые классы подгружаются в момент старта приложения. В нашем примере мы использовали нажатия кнопок для запуска и остановки анимации, поэтому нам удалось избежать этой проблемы. А что же делать, если нам необходимо запустить анимацию сразу?

В этом случае придётся усложнить код. Добавим новый класс:


class Starter implements Runnable {
        public void run() {
            mFrameAnimation.start();
        }
}

А в основном классе из первого примера добавим строчку кода:


image.post(new Starter());

Теперь при запуске приложения анимация автоматически начнет проигрываться.

Также можно попробовать вызвать метод onWindowFocusChanged().

Ниже есть пример, где код вынесен в onResume().

Копирование кадров и показ в обратном порядке

Мы можем получить отдельные кадры анимации и выстроить новую цепочку. Добавим на экран второй компонент ImageView и запустим в нём анимацию в обратном порядке, скопировав все кадры из первой анимации.


package ru.alexanderklimov.testapplication;

import ...

public class MainActivity extends ActionBarActivity {

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        ImageView imageView = findViewById(R.id.imageView);
        imageView.setBackgroundResource(R.drawable.catanimation);

        ImageView secondImageView = findViewById(R.id.imageView2);

        final AnimationDrawable sourceAnimationDrawable
                = (AnimationDrawable) imageView.getBackground();

        // Копируем кадры в обратном порядке
        final AnimationDrawable reversedAnimationDrawable = copyReversedAnimDrawable(sourceAnimationDrawable);
        secondImageView.setImageDrawable(reversedAnimationDrawable);

        final Button startButton = findViewById(R.id.buttonStart);
        startButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                if (!reversedAnimationDrawable.isRunning()) {
                    sourceAnimationDrawable.start();
                    reversedAnimationDrawable.start();
                }
            }
        });

        final Button stopButton = findViewById(R.id.buttonStop);
        stopButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                if (reversedAnimationDrawable.isRunning()) {
                    sourceAnimationDrawable.stop();
                    reversedAnimationDrawable.stop();
                }
            }
        });
    }

    private AnimationDrawable copyReversedAnimDrawable(AnimationDrawable src) {
        AnimationDrawable newAnimationDrawable = new AnimationDrawable();

        int numberOfFrame = src.getNumberOfFrames();

        for (int i = 0; i < numberOfFrame; i++) {
            newAnimationDrawable.addFrame(
                    src.getFrame(numberOfFrame - i - 1),
                    src.getDuration(numberOfFrame - i - 1));
        }
        newAnimationDrawable.setOneShot(false);

        return newAnimationDrawable;
    }
}

На всякий случай предупрежу, что во всех примерах мы использовали фон: setBackgroundResource(), getBackground() и т.д. Если вы будете использовать атрибут android:src, то соответственно используйте getDrawable() и другие родственные методы.

У AnimationDrawable нет слушателей, позволяющих отслеживать конец анимации. Как вариант, рассчитайте самостоятельно количество кадров и их продолжительность и реализуйте свой способ.

Данный вид анимации подходит для небольших изображений с ограниченным количеством кадров. Если изображения будут большими и кадров слишком много, то наступит переполнение памяти. Помните об этом.

Анимация градиентного фона

Создадим ресурсы для цветов в colors.xml.


<color name="purple_500">#9C27B0</color>
<color name="deep_purple_500">#673AB7</color>
<color name="indigo_500">#3F51B5</color>
<color name="blue_500">#2196F3</color>
<color name="cyan_500">#00BCD4</color>
<color name="light_blue_500">#03A9F4</color>

В папке drawable создайте три файла с именами gradient_1.xml, gradient_2.xml и т.д. Содержимое будет одинаковым, только меняйте имена ресурсов и углы: 0, 255, 45.


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <gradient
        android:startColor="@color/deep_purple_500"
        android:endColor="@color/purple_500"
        android:angle="0"/>
</shape>

В той же папке drawable создайте ещё один файл gradient_animation_list.xml


<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:drawable="@drawable/gradient_1"
        android:duration="5000"/>

    <item
        android:drawable="@drawable/gradient_2"
        android:duration="5000"/>

    <item
        android:drawable="@drawable/gradient_3"
        android:duration="5000"/>

</animation-list>

Присвойте корневому контейнеру идентификатор и созданный ресурс, у меня корневым макетом был FrameLayout.


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/layout_root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/gradient_animation_list">

Осталось написать код.


package ru.alexanderklimov.animation;

import android.graphics.drawable.AnimationDrawable;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.FrameLayout;

public class MainActivity extends AppCompatActivity  {

    private AnimationDrawable mAnimationDrawable;
    FrameLayout layout;

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

        layout = (FrameLayout) findViewById(R.id.layout_root);

        mAnimationDrawable = (AnimationDrawable) layout.getBackground();
        mAnimationDrawable.setEnterFadeDuration(2500);
        mAnimationDrawable.setExitFadeDuration(5000);
    }

    @Override
    protected void onResume() {
        super.onResume();

        mAnimationDrawable.start();
    }
}

Дополнительное чтение

Реклама