ActionScript 用の有名なアニメーションライブラリである Tweener を Android で使用する方法について説明します。
Tweener を使用すると、
Tweener.to(imageView, 3000, "alpha", 0);
のような感じでプロパティアニメーションさせたり、
Tweener.to(imageView, // アニメーション対象
3000, // アニメーション期間(単位はミリ秒)
"translationX", 200.0f, // 変化させたいプロパティ名, 値
"translationY", 100.0f, // 変化させたいプロパティ名, 値
"ease", Ease.Quad.easeInOut, // 補間関数
"onComplete", new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// 必要であれば、アニメーション終了時のコードを書きます
}
});
のような感じで、複雑なアニメーション(複数プロパティ、補完関数変更、終了イベント補足)も、短いコードで書くこともできます。
Android 3.0 以降であれば、Android でもプロパティアニメーションがサポートされているため、
新たに Tweener を使用するまでもないのですが、Tweener の方が馴染みがある場合には、役に立つかもしれません。
ちなみにプロパティアニメーションについては『Androidプログラミング上達読本』にも解説があるので、興味を持った方は読んでみてください^^;
使い方
1. AOSP から Android 4.0 以降のソースコードをダウンロードする。
※必要なコードは frameworks 下の一部のコードだけなので repo ではなく、git で直接取得してもよいです
git clone https://android.googlesource.com/platform/frameworks/base
2. 取得したコードの中から以下のクラスをコピーし、任意のパッケージ名に変更する。
- frameworks\base\core\java\com\android\internal\widget\multiwaveview\Tweener.java
- frameworks\base\core\java\com\android\internal\widget\multiwaveview\Ease.java
※Tweener クラスはパッケージスコープになっているので必要に応じてアクセスレベルを変更してください
※パッケージ名を変更しないと、4.0(ICS)―4.1(JB)間で内部クラスが変更されているので上手く動作しない可能性があります
3. Tweener.to() を呼び出し、Tweener アニメーションコードを書く。
※変化させるプロパティ名は、ActionScript でなく、Adnroid で使用できるプロパティ名を書きます
※Tweener.from() などは移植されていないようです
Tweener
- tweener - A class for creating tweens in actionscript 2 and 3 - because there's infinity between 0 and 1. - Google Project Hosting
ActionScript は言語レベルでプロパティをサポートされているので、Tweener の実装は非常にシンプルなコードになっています。
Android 4.0 フレームワークの内部クラスで実装されている Tweener のコード
- Twneer.java
Android のプロパティアニメーションAPI(PropertyValuesHolder、ObjectAnimator など)を使用して移植されています。
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.widget.multiwaveview;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import android.animation.Animator.AnimatorListener;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.util.Log;
class Tweener {
private static final String TAG = "Tweener";
private static final boolean DEBUG = false;
ObjectAnimator animator;
private static HashMap<Object, Tweener> sTweens = new HashMap<Object, Tweener>();
public Tweener(ObjectAnimator anim) {
animator = anim;
}
private static void remove(Animator animator) {
Iterator<Entry<Object, Tweener>> iter = sTweens.entrySet().iterator();
while (iter.hasNext()) {
Entry<Object, Tweener> entry = iter.next();
if (entry.getValue().animator == animator) {
if (DEBUG) Log.v(TAG, "Removing tweener " + sTweens.get(entry.getKey())
+ " sTweens.size() = " + sTweens.size());
iter.remove();
break; // an animator can only be attached to one object
}
}
}
public static Tweener to(Object object, long duration, Object... vars) {
long delay = 0;
AnimatorUpdateListener updateListener = null;
AnimatorListener listener = null;
TimeInterpolator interpolator = null;
// Iterate through arguments and discover properties to animate
ArrayList<PropertyValuesHolder> props = new ArrayList<PropertyValuesHolder>(vars.length/2);
for (int i = 0; i < vars.length; i+=2) {
if (!(vars[i] instanceof String)) {
throw new IllegalArgumentException("Key must be a string: " + vars[i]);
}
String key = (String) vars[i];
Object value = vars[i+1];
if ("simultaneousTween".equals(key)) {
// TODO
} else if ("ease".equals(key)) {
interpolator = (TimeInterpolator) value; // TODO: multiple interpolators?
} else if ("onUpdate".equals(key) || "onUpdateListener".equals(key)) {
updateListener = (AnimatorUpdateListener) value;
} else if ("onComplete".equals(key) || "onCompleteListener".equals(key)) {
listener = (AnimatorListener) value;
} else if ("delay".equals(key)) {
delay = ((Number) value).longValue();
} else if ("syncWith".equals(key)) {
// TODO
} else if (value instanceof float[]) {
props.add(PropertyValuesHolder.ofFloat(key,
((float[])value)[0], ((float[])value)[1]));
} else if (value instanceof Number) {
float floatValue = ((Number)value).floatValue();
props.add(PropertyValuesHolder.ofFloat(key, floatValue));
} else {
throw new IllegalArgumentException(
"Bad argument for key \"" + key + "\" with value " + value.getClass());
}
}
// Re-use existing tween, if present
Tweener tween = sTweens.get(object);
ObjectAnimator anim = null;
if (tween == null) {
anim = ObjectAnimator.ofPropertyValuesHolder(object,
props.toArray(new PropertyValuesHolder[props.size()]));
tween = new Tweener(anim);
sTweens.put(object, tween);
if (DEBUG) Log.v(TAG, "Added new Tweener " + tween);
} else {
anim = sTweens.get(object).animator;
replace(props, object); // Cancel all animators for given object
}
if (interpolator != null) {
anim.setInterpolator(interpolator);
}
// Update animation with properties discovered in loop above
anim.setStartDelay(delay);
anim.setDuration(duration);
if (updateListener != null) {
anim.removeAllUpdateListeners(); // There should be only one
anim.addUpdateListener(updateListener);
}
if (listener != null) {
anim.removeAllListeners(); // There should be only one.
anim.addListener(listener);
}
anim.addListener(mCleanupListener);
anim.start();
return tween;
}
Tweener from(Object object, long duration, Object... vars) {
// TODO: for v of vars
// toVars[v] = object[v]
// object[v] = vars[v]
return Tweener.to(object, duration, vars);
}
// Listener to watch for completed animations and remove them.
private static AnimatorListener mCleanupListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
remove(animation);
}
@Override
public void onAnimationCancel(Animator animation) {
remove(animation);
}
};
public static void reset() {
if (DEBUG) {
Log.v(TAG, "Reset()");
if (sTweens.size() > 0) {
Log.v(TAG, "Cleaning up " + sTweens.size() + " animations");
}
}
sTweens.clear();
}
private static void replace(ArrayList<PropertyValuesHolder> props, Object... args) {
for (final Object killobject : args) {
Tweener tween = sTweens.get(killobject);
if (tween != null) {
tween.animator.cancel();
if (props != null) {
tween.animator.setValues(
props.toArray(new PropertyValuesHolder[props.size()]));
} else {
sTweens.remove(tween);
}
}
}
}
}
- Ease.java
Tweener で定義されているイージングを Android のプロパティアニメーションAPI を使用して移植してあります。
TimeInterpolator を継承したクラスなので、ここだけでも使えますね!
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.widget.multiwaveview;
import android.animation.TimeInterpolator;
class Ease {
private static final float DOMAIN = 1.0f;
private static final float DURATION = 1.0f;
private static final float START = 0.0f;
static class Linear {
public static final TimeInterpolator easeNone = new TimeInterpolator() {
public float getInterpolation(float input) {
return input;
}
};
}
static class Cubic {
public static final TimeInterpolator easeIn = new TimeInterpolator() {
public float getInterpolation(float input) {
return DOMAIN*(input/=DURATION)*input*input + START;
}
};
public static final TimeInterpolator easeOut = new TimeInterpolator() {
public float getInterpolation(float input) {
return DOMAIN*((input=input/DURATION-1)*input*input + 1) + START;
}
};
public static final TimeInterpolator easeInOut = new TimeInterpolator() {
public float getInterpolation(float input) {
return ((input/=DURATION/2) < 1.0f) ?
(DOMAIN/2*input*input*input + START)
: (DOMAIN/2*((input-=2)*input*input + 2) + START);
}
};
}
static class Quad {
public static final TimeInterpolator easeIn = new TimeInterpolator() {
public float getInterpolation (float input) {
return DOMAIN*(input/=DURATION)*input + START;
}
};
public static final TimeInterpolator easeOut = new TimeInterpolator() {
public float getInterpolation(float input) {
return -DOMAIN *(input/=DURATION)*(input-2) + START;
}
};
public static final TimeInterpolator easeInOut = new TimeInterpolator() {
public float getInterpolation(float input) {
return ((input/=DURATION/2) < 1) ?
(DOMAIN/2*input*input + START)
: (-DOMAIN/2 * ((--input)*(input-2) - 1) + START);
}
};
}
static class Quart {
public static final TimeInterpolator easeIn = new TimeInterpolator() {
public float getInterpolation(float input) {
return DOMAIN*(input/=DURATION)*input*input*input + START;
}
};
public static final TimeInterpolator easeOut = new TimeInterpolator() {
public float getInterpolation(float input) {
return -DOMAIN * ((input=input/DURATION-1)*input*input*input - 1) + START;
}
};
public static final TimeInterpolator easeInOut = new TimeInterpolator() {
public float getInterpolation(float input) {
return ((input/=DURATION/2) < 1) ?
(DOMAIN/2*input*input*input*input + START)
: (-DOMAIN/2 * ((input-=2)*input*input*input - 2) + START);
}
};
}
static class Quint {
public static final TimeInterpolator easeIn = new TimeInterpolator() {
public float getInterpolation(float input) {
return DOMAIN*(input/=DURATION)*input*input*input*input + START;
}
};
public static final TimeInterpolator easeOut = new TimeInterpolator() {
public float getInterpolation(float input) {
return DOMAIN*((input=input/DURATION-1)*input*input*input*input + 1) + START;
}
};
public static final TimeInterpolator easeInOut = new TimeInterpolator() {
public float getInterpolation(float input) {
return ((input/=DURATION/2) < 1) ?
(DOMAIN/2*input*input*input*input*input + START)
: (DOMAIN/2*((input-=2)*input*input*input*input + 2) + START);
}
};
}
static class Sine {
public static final TimeInterpolator easeIn = new TimeInterpolator() {
public float getInterpolation(float input) {
return -DOMAIN * (float) Math.cos(input/DURATION * (Math.PI/2)) + DOMAIN + START;
}
};
public static final TimeInterpolator easeOut = new TimeInterpolator() {
public float getInterpolation(float input) {
return DOMAIN * (float) Math.sin(input/DURATION * (Math.PI/2)) + START;
}
};
public static final TimeInterpolator easeInOut = new TimeInterpolator() {
public float getInterpolation(float input) {
return -DOMAIN/2 * ((float)Math.cos(Math.PI*input/DURATION) - 1.0f) + START;
}
};
}
}
あわせて読みたい






























