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

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

Шкодим

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

TranslateAnimation и <translate>

Программная анимация

Класс TranslateAnimation наследуется от класса Animation и отвечает за анимацию перемещения объекта.

Один из конструкторов класса TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) создаёт анимацию путём указания начальных и конечных координат.

Также есть схожий конструктор TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue, int fromYType, float fromYValue, int toYType, float toYValue), где дополнительно можно указать тип координат. Тип может принимать одно из следующих значений:

  • Animation.ABSOLUTE - абсолютные координаты (координаты экрана), указываются пиксели
  • Animation.RELATIVE_TO_SELF - значения относительно текущих координат объекта, указываются проценты, где 1.0f это 100%
  • Animation.RELATIVE_TO_PARENT - значения относительно родительских координат, указываются проценты, где 1.0f это 100%

Слева направо

Простейшая анимация сдвига ImageView слева направо.


ImageView image = findViewById(R.id.image);
TranslateAnimation animation = new TranslateAnimation(-400, 50, 50, 50);
animation.setDuration(1000);
animation.setFillAfter(true);
image.startAnimation(animation);

Пример для кнопки на Kotlin


button.startAnimation(TranslateAnimation(0f, 300f,
        0f, 0f)
        .apply {
            duration = 1000
        })

Сверху вниз (XML)

Рассмотрим пример перемещения объекта ImageView сверху вниз (падать). В папке res/anim создадим файл falling.xml:


<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/linear_interpolator" >

    <translate
        android:duration="2000"
        android:fromYDelta="-100%"
        android:toYDelta="700" />

</set>

Разместим на форме кнопку и ImageView и добавим код:


package ru.alexanderklimov.testapp;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.ImageView;

public class TestAppActivity extends Activity {

	private ImageView mImageView;

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		mImageView = findViewById(R.id.imageView1);

		Button startFallingButton = (Button) findViewById(R.id.starttranslate);

		final Animation fallingAnimation = AnimationUtils.loadAnimation(this,
				R.anim.falling);
		
		startFallingButton.setOnClickListener(new Button.OnClickListener(){
			   @Override
			   public void onClick(View arg0) {
			    mImageView.startAnimation(fallingAnimation);
			   }});
	}
}

Запускайте проект и любуйтесь эффектом.

Усложним проект и заставим падать несколько объектов по очереди. Создадим два новых файла анимации:

falling_down.xml


<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromYDelta="-50%"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:toYDelta="0.0" />

falling_up.xml


<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromYDelta="0.0"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:toYDelta="50%" />

Подготовим разметку из трёх невидимых ImageView:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >

    <ImageView
        android:id="@+id/image1"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:src="@drawable/ic_launcher"
        android:visibility="invisible" />

    <ImageView
        android:id="@+id/image2"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:src="@drawable/ic_launcher"
        android:visibility="invisible" />

    <ImageView
        android:id="@+id/image3"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:src="@drawable/ic_launcher"
        android:visibility="invisible" />

</LinearLayout>

Напишем код:


package ru.alexanderklimov.falling;

import ...

public class MainActivity extends Activity {

	private ImageView mImageView1, mImageView2, mImageView3;
	private Animation fallingAnimation, upAnimation;
	private ImageView curFallingImageView;

	@Override
	public void onCreate(Bundle icicle) {
		super.onCreate(icicle);

		setContentView(R.layout.activity_main);

		mImageView1 = (ImageView) findViewById(R.id.image1);
		mImageView2 = (ImageView) findViewById(R.id.image2);
		mImageView3 = (ImageView) findViewById(R.id.image3);

		fallingAnimation = AnimationUtils.loadAnimation(this,
				R.anim.falling_down);
		upAnimation = AnimationUtils.loadAnimation(this, R.anim.falling_up);

		fallingAnimation.setAnimationListener(animationFallingListener);
		upAnimation.setAnimationListener(animationUpListener);

		curFallingImageView = mImageView1;
		mImageView1.startAnimation(fallingAnimation);
		mImageView1.setVisibility(View.VISIBLE);
	}

	@Override
	protected void onPause() {
		super.onPause();
		mImageView1.clearAnimation();
		mImageView2.clearAnimation();
		mImageView3.clearAnimation();
	}

	AnimationListener animationFallingListener = new AnimationListener() {

		@Override
		public void onAnimationEnd(Animation arg0) {
			// TODO Auto-generated method stub
			if (curFallingImageView == mImageView1) {
				mImageView1.startAnimation(upAnimation);
			} else if (curFallingImageView == mImageView2) {
				mImageView2.startAnimation(upAnimation);
			} else if (curFallingImageView == mImageView3) {
				mImageView3.startAnimation(upAnimation);
			}
		}

		@Override
		public void onAnimationRepeat(Animation animation) {
		}

		@Override
		public void onAnimationStart(Animation animation) {
		}
	};

	AnimationListener animationUpListener = new AnimationListener() {

		@Override
		public void onAnimationEnd(Animation animation) {
			if (curFallingImageView == mImageView1) {
				curFallingImageView = mImageView2;
				mImageView2.startAnimation(fallingAnimation);
				mImageView1.setVisibility(View.INVISIBLE);
				mImageView2.setVisibility(View.VISIBLE);
				mImageView3.setVisibility(View.INVISIBLE);
			} else if (curFallingImageView == mImageView2) {
				curFallingImageView = mImageView3;
				mImageView3.startAnimation(fallingAnimation);
				mImageView1.setVisibility(View.INVISIBLE);
				mImageView2.setVisibility(View.INVISIBLE);
				mImageView3.setVisibility(View.VISIBLE);
			} else if (curFallingImageView == mImageView3) {
				curFallingImageView = mImageView1;
				mImageView1.startAnimation(fallingAnimation);
				mImageView1.setVisibility(View.VISIBLE);
				mImageView2.setVisibility(View.INVISIBLE);
				mImageView3.setVisibility(View.INVISIBLE);
			}
		}

		@Override
		public void onAnimationRepeat(Animation animation) {
		}

		@Override
		public void onAnimationStart(Animation animation) {
		}
	};
}

Запустив проект, вы увидите, как сверху будут сыпаться картинки.

Движение по диагонали

По такому же принципу можно организовать движение по диагонали, изменяя одновременно значения X и Y. Создадим ещё один файл res/anim/slide_diagonal.xml:


<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromYDelta="0"
        android:toYDelta="100%p"/>
    <translate
        android:fromXDelta="0"
        android:toXDelta="100%p"/>
</set>

Хотя эта запись избыточна, можно и так:


<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="100%p"
        android:toYDelta="100%p" />
</set>

Разместите в углу экрана ImageView внутри контейнера LinearLayout, чтобы у него была возможность двигаться из угла в угол. Напишем код.


package ru.alexanderklimov.test;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;


public class MainActivity extends Activity {

    private ImageView mImageView;
    private Animation mDiagonalAnimation;

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

        mImageView = findViewById(R.id.imageView);

        mDiagonalAnimation = AnimationUtils.loadAnimation(this, R.anim.slide_diagonal);
        mDiagonalAnimation.setDuration(3000);

        mDiagonalAnimation.setAnimationListener(animationListener);
        mImageView.startAnimation(mDiagonalAnimation);
    }

    Animation.AnimationListener animationListener = new Animation.AnimationListener() {
        @Override
        public void onAnimationStart(Animation animation) {

        }

        @Override
        public void onAnimationEnd(Animation animation) {
            mImageView.startAnimation(mDiagonalAnimation);
        }

        @Override
        public void onAnimationRepeat(Animation animation) {

        }
    };
}

Дрожь земли

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

Суть анимации состоит в циклическом повторении операции перемещения объекта за короткий промежуток времени, что создаёт иллюзию дрожания. Создадим папку res/anim, в которой надо создать два файла: shake.xml и cycle_7.xml.

shake.xml


<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromXDelta="0"
    android:interpolator="@anim/cycle_7"
    android:toXDelta="10" />

cycle_7.xml


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

В этом примере мы сдвигаем объект на 10 пикселей и повторяем подобное смещение 7 раз. Этого вполне достаточно для примера. Вы можете придумать свою анимацию.

Разместим на поверхности активности кнопку и заставим её дрожать при нажатии.


package ru.alexanderklimov.shake;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;

public class ShakeDemoActivity extends Activity {

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		final Button shakeButton = findViewById(R.id.button1);

		final Animation shakeAnimation = AnimationUtils.loadAnimation(this, R.anim.shake);

		shakeButton.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View view) {
				shakeButton.startAnimation(shakeAnimation);
			}
		});
	}
}

Ничего нам не мешает применить эффект дрожания и к целому экрану (разметка LinearLayout). Добавим пару строчек кода


final View linear = (View) findViewById(R.id.linear);

linear.startAnimation(shakeAnimation);
// shakeButton.startAnimation(shakeAnimation);

Как видите, ничего сложного здесь нет. Кстати, в документации к Android также используется подобный приём. Предположим, у нас есть два текстовых поля для ввода логина и пин-кода. Если пользователь вводит пин-код, длиной меньше или больше, чем четыре символа — окно ввода сотрясается. При правильном вводе пин-кода переходим на следующее окно.


final EditText editLogin = (EditText) this.findViewById(R.id.login);
final EditText editPin = (EditText) this.findViewById(R.id.pin);

shakeButton.setOnClickListener(new OnClickListener() {
	@Override
	public void onClick(View v) {
		// linear.startAnimation(shakeAnimation);
		// shakeButton.startAnimation(a);
		// Копируем пин-код который ввел пользователь в переменную pass
		String pass = editPin.getText().toString();
		// Проверяем равна ли длина 4
		if (pass.length() == 4) {
			// Выводим всплывающее окно
			Toast toast = Toast.makeText(getApplicationContext(),
					"Добро пожаловать, "
							+ editLogin.getText().toString(),
					Toast.LENGTH_SHORT);
			toast.show();

			// Создаем намерение для перехода на другую активность
			// Intent intent = new Intent();
			// intent.setClass(ShakeDemoActivity.this,
			// WelcomeAndroid.class);
			// startActivity(intent);
			// // Закрываем текущий Activity
			// finish();
		} else {
			// Если пин-код неправильной длины, то применияем анимацию
			editPin.startAnimation(shakeAnimation);
			// Выводем всплывающее окно "Wrong pin, must be 4 digits"

			Toast toast = Toast.makeText(getApplicationContext(),
					"Неверный пин-код, должно быть четыре цифры",
					Toast.LENGTH_SHORT);
			toast.show();
			// Очищаем форму ввода пин кода
			editPin.setText("");
		}
	}
});

Эффект дрожания

Слайд-шоу

Эффект слайд-шоу, когда один элемент выталкивает другой элемент, реализуется двумя анимациями. Вот пример движения слайдов слева направо. Разместите в папке res/anim файлы:

Файл out.xml


<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
	android:shareInterpolator="false">
    <translate
    	android:fromXDelta="0%" 
		android:toXDelta="100%"
        android:fromYDelta="0%" 
		android:toYDelta="0%"
        android:duration="500"
    />
</set>

Файл in.xml


<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
	android:shareInterpolator="false">
    <translate
    	android:fromXDelta="-100%" 
		android:toXDelta="0%"
        android:fromYDelta="0%"    
		android:toYDelta="0%"
        android:duration="500"
    />
</set>

Такую анимацию можно увидеть в примере с элементом ViewFlipper

По значениям атрибутов android:fromXDelta и android:toXDelta можно видеть, что идёт смещение первого объекта от 0% до 100%, а второго объекта от -100% до 0.

Если вы хотите использовать анимацию в противоположном направлении, то поменяйте значения на нужные:


<!-- out.xml -->
android:fromXDelta="0%"
android:toXDelta="-100%"

<!-- in.xml -->
android:fromXDelta="100%"
android:toXDelta="0%

Используем системные ресурсы анимации

В Android есть уже предустановленные ресурсы для анимации эффекта слайд-шоу, поэтому необязательно создавать собственные XML-файлы, если вас устраивают системные параметры для показа слайдов. Код изменится незначительно (из примера Анимация переходов):


// Вместо
//    Animation animationFlipIn = AnimationUtils.loadAnimation(this,
//            R.anim.flipin);
//    Animation animationFlipOut = AnimationUtils.loadAnimation(this,
//            R.anim.flipout);

// Можно использовать 
final Animation animationFlipIn = AnimationUtils.loadAnimation(this,
        android.R.anim.slide_in_left);
final Animation animationFlipOut = AnimationUtils.loadAnimation(this,
    android.R.anim.slide_out_right);

В этом примере задействованы ресурсы android.R.anim.slide_out_right и android.R.anim.slide_in_left, которые создают эффект слайд-шоу слева-направо.

Реклама