Освой Android играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
Новые интерполяторы из библиотеки поддержки
Атрибут android:interpolator в файле анимации определяет постоянную скорости, которая описывает динамику визуальной деятельности в зависимости от времени. Интерполяторы часто используют в различных библиотеках для создания реалистичных анимаций. Например, любой кот знаком с анимацией падающего мячика, который после каждого отскока поднимается на меньшую высоту и постепенно остаётся лежать неподвижным на земле. Другой пример - вы дёргаете за ручку ящика, который постоянно заедает в письменном столе - сначала вы несколько раз безуспешно дёргаете за ручку, а потом рывком открываете ящик. Очень много подобных видов интерполяторов есть в JavaScript-библиотеке jQuery. В Android встроенных интерполяторов поменьше. Рассмотрим их на примерах.
Если представить себе переход от состояния "от" до состояния "до" в виде прямой линии, то интерполятор определяет, в какой точке этой линии будет находиться переход в любой момент времени. Самый простой интерполятор - линейный. Он делит прямую на равные отрезки и осуществляет равномерное перемещение по этим отрезкам на протяжении указанного времени. В результате объект движется без ускорения от первого состояния до второго.
По умолчанию, используется интерполятор accelerate_decelerate_interpolator, в котором используется плавное ускорение, а затем плавное замедление.
Интерполятор bounce_interpolator - это стандартный отскок, когда объект доходит до конечной точки и отскакивает обратно к начальной точке. И такое действие повторяется несколько раз.
Создадим в папке res/anim 9 файлов для каждого вида интерполятора.
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator" >
<translate
android:duration="2000"
android:fromYDelta="-100%p"
android:toYDelta="0" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/decelerate_interpolator" >
<translate
android:duration="2000"
android:fromYDelta="-100%p"
android:toYDelta="0" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator" >
<translate
android:duration="2000"
android:fromYDelta="-100%p"
android:toYDelta="0" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/anticipate_interpolator" >
<translate
android:duration="2000"
android:fromYDelta="-80%p"
android:toYDelta="0" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/anticipate_overshoot_interpolator" >
<translate
android:duration="2000"
android:fromYDelta="-80%p"
android:toYDelta="0" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/overshoot_interpolator" >
<translate
android:duration="2000"
android:fromYDelta="-80%p"
android:toYDelta="0" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/bounce_interpolator" >
<translate
android:duration="2000"
android:fromYDelta="-100%p"
android:toYDelta="0" />
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/cycle_interpolator" >
<translate
android:duration="2000"
android:fromYDelta="-50%p"
android:toYDelta="0" />
</set>
<?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%p"
android:toYDelta="0" />
</set>
Теперь на главном экране приложения разместим кнопки для запуска каждого вида анимации и 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="vertical" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:layout_margin="30dp"
android:gravity="bottom"
android:orientation="vertical" >
<ImageView
android:id="@+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@drawable/ic_launcher" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<Button
android:id="@+id/bounce"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Bounce" />
<Button
android:id="@+id/accelerate"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Accelerate" />
<Button
android:id="@+id/decelerate"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Decelerate" />
<Button
android:id="@+id/acceleratedecelerate"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Accelerate_Decelerate" />
<Button
android:id="@+id/anticipate"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Anticipate" />
<Button
android:id="@+id/anticipateovershoot"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Anticipate_Overshoot" />
<Button
android:id="@+id/overshoot"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Overshoot" />
<Button
android:id="@+id/cycle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Cycle" />
<Button
android:id="@+id/linear"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Linear" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
Пишем код для каждого вида анимаций. Несмотря на большое количество строк, код очень простой. Создаем объект Animation для каждого вида анимации и запускаем созданную анимацию при нажатии соответствующей кнопки.
package ru.alexanderklimov.interpolator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.BounceInterpolator;
import android.view.animation.TranslateAnimation;
import android.widget.Button;
import android.widget.ImageView;
public class InterpolatorActivity extends Activity {
ImageView image;
Animation animAccelerate;
Animation animDecelerate;
Animation animAccelerateDecelerate;
Animation animAnticipate;
Animation animAnticipateOvershoot;
Animation animOvershoot;
Animation animBounce;
Animation animCycle;
Animation animLinear;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
animAccelerate = AnimationUtils.loadAnimation(this, R.anim.accelerate);
animDecelerate = AnimationUtils.loadAnimation(this, R.anim.decelerate);
animAccelerateDecelerate = AnimationUtils.loadAnimation(this,
R.anim.accelerate_decelerate);
animAnticipate = AnimationUtils.loadAnimation(this, R.anim.anticipate);
animAnticipateOvershoot = AnimationUtils.loadAnimation(this,
R.anim.anticipate_overshoot);
animOvershoot = AnimationUtils.loadAnimation(this, R.anim.overshoot);
animBounce = AnimationUtils.loadAnimation(this, R.anim.bounce);
animCycle = AnimationUtils.loadAnimation(this, R.anim.cycle);
animLinear = AnimationUtils.loadAnimation(this, R.anim.linear);
image = (ImageView) findViewById(R.id.image);
}
public void onButtonClick(View v) {
switch (v.getId()) {
case R.id.accelerate:
image.startAnimation(animAccelerate);
break;
case R.id.decelerate:
image.startAnimation(animDecelerate);
break;
case R.id.acceleratedecelerate:
image.startAnimation(animAccelerateDecelerate);
break;
case R.id.anticipate:
image.startAnimation(animAnticipate);
break;
case R.id.anticipateovershoot:
image.startAnimation(animAnticipateOvershoot);
break;
case R.id.overshoot:
image.startAnimation(animOvershoot);
break;
case R.id.bounce:
// image.startAnimation(animBounce);
TranslateAnimation tAnim = new TranslateAnimation(0, 0, -700, 10);
tAnim.setInterpolator(new BounceInterpolator());
tAnim.setDuration(1000);
image.startAnimation(tAnim);
break;
case R.id.cycle:
image.startAnimation(animCycle);
break;
case R.id.linear:
image.startAnimation(animLinear);
break;
}
}
}
Для разнообразия у кнопки с анимацией Bounce (любимая анимация котов) я привёл программное использование объектов TranslateAnimation и BounceInterpolator.
Для закрепления материала создадим ещё один проект. Нам понадобится разметка с текстовыми метками:
<?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:gravity="center_horizontal"
android:orientation="vertical" >
<TextView
android:id="@+id/firstTextView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="Кот"
android:textSize="42sp" />
<TextView
android:id="@+id/secondTextView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="Который"
android:textSize="42sp" >
</TextView>
<TextView
android:id="@+id/thirdTextView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="Гулял"
android:textSize="42sp" >
</TextView>
<TextView
android:id="@+id/fourthTextView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="Сам"
android:textSize="42sp" >
</TextView>
<TextView
android:id="@+id/fifthTextView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="По себе"
android:textSize="42sp" >
</TextView>
</LinearLayout>
Создадим два файла анимации в папке res/anim.
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >
<rotate
android:interpolator="@android:anim/anticipate_overshoot_interpolator"
android:duration="3000"
android:fromDegrees="359"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="0" />
<scale
android:interpolator="@android:anim/accelerate_interpolator"
android:duration="3000"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:toXScale="2.0"
android:toYScale="2.0" >
</scale>
</set>
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator" >
<translate
android:duration="3000"
android:fromXDelta="-200%"
android:fromYDelta="200%"
android:toXDelta="0%"
android:toYDelta="0%"
android:zAdjustment="top" />
<alpha
android:duration="6000"
android:fromAlpha="0.0"
android:toAlpha="1.0" />
</set>
Обратите внимание, что в первом файле интерполяторы заданы отдельно для каждой анимации, а во втором - для секции set.
Напишем отдельный метод, который можно вызывать в методе активности onCreate():
private void RunAnimations() {
Animation a = AnimationUtils.loadAnimation(this, R.anim.translatealpha);
a.reset();
TextView tv = (TextView) findViewById(R.id.firstTextView);
tv.clearAnimation();
tv.startAnimation(a);
a = AnimationUtils.loadAnimation(this, R.anim.translatealpha);
a.reset();
tv = (TextView) findViewById(R.id.secondTextView);
tv.clearAnimation();
tv.startAnimation(a);
a = AnimationUtils.loadAnimation(this, R.anim.scalerotate);
a.reset();
tv = (TextView) findViewById(R.id.thirdTextView);
tv.clearAnimation();
tv.startAnimation(a);
a = AnimationUtils.loadAnimation(this, R.anim.translatealpha);
a.reset();
tv = (TextView) findViewById(R.id.fourthTextView);
tv.clearAnimation();
tv.startAnimation(a);
a = AnimationUtils.loadAnimation(this, R.anim.scalerotate);
a.reset();
tv = (TextView) findViewById(R.id.fifthTextView);
tv.clearAnimation();
tv.startAnimation(a);
}
На экране вы увидите следующую картинку
Создадим собственный простейший компонент BounceView, который будет выводить текст с анимацией в месте касания экрана.
package ru.alexanderklimov.bounce;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.BounceInterpolator;
import android.view.animation.TranslateAnimation;
public class BounceView extends View {
float currentX;
float currentY;
public BounceView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
if (currentX > 0 && currentY > 0) {
Paint p = new Paint();
p.setColor(Color.RED);
p.setTextSize(20);
canvas.drawText("Туда-сюда", currentX, currentY, p);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
currentX = event.getX();
currentY = event.getY();
TranslateAnimation translateanim = new TranslateAnimation(-100, 0, 0, 0);
translateanim.setInterpolator(new BounceInterpolator());
translateanim.setDuration(1000);
startAnimation(translateanim);
invalidate();
return true;
}
}
Подключаем компонент в основной активности:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BounceView bounce = new BounceView(this);
setContentView(bounce);
}
Запустите проект и посмотрите на эффект.
В библиотеке поддержки v4 Support Library появились новые интерполяторы, которые разработаны с учётом Material Design.
Использование интерполяторов делает анимацию более реалистичной. Берите пример с котов!
Codedependent: DevBytes: Anticipation and Overshoot, Part 1
Understanding Interpolators in Android