package com.android_mvc.framework.ui.anim.desc;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
/**
* 順番に実行したいアニメーションの説明を記述。
* @author id:language_and_engineering
*
*/
public class AnimationDescription {
// アニメーションの適用対象
public View[] new_target_views = null;
public RelativeLayout root_layout;
// 開始前の待機時間
public int wait_before = 0;
// アニメーションの持続時間
public int anim_duration = 0;
// 終了後の待機時間
public int wait_after = 0;
// 終了後の補正処理を行うかどうか
public boolean exec_modify_flag = true;
// アニメーション対象UI
public ArrayList<View> current_target_views;
public Activity target_activity;
// 制御スレッド側でUIスレッドを追い越さないためのゆとり時間
private int duration_rest = 200;
// --------- ユーザ定義用 -----------
/**
* 具体的なアニメーションを定義。
* AnimationまたはAnimationSetを返すこと。
* ユーザが簡潔に記述する。
*/
protected Animation describe() {
// Override me
return null;
}
/**
* describeの複数版。
*/
protected List<Animation> describeAsList()
{
// Override me
return null;
}
/**
* アニメーション後に各種属性を変更(setFillAfter()が効かない問題への対処)
* ※この補正処理が行われないように禁止することも可能。
*/
protected void modifyAfterAnimation(View v) {
// Override me
}
/**
* アニメーション実行前のイベントハンドラ。
* UIスレッド上で任意の処理を記載可能。
*/
protected void beforeAnimate()
{
// Override me
}
/**
* アニメーション実行後のイベントハンドラ。
* UIスレッド上で任意の処理を記載可能。
*/
protected void afterAnimate()
{
// Override me
}
// --------- setter -----------
/**
* アニメーションの適用対象をセット
*/
public AnimationDescription targetViews(View...views) {
this.new_target_views = views;
return this;
}
/**
* アニメーション開始前の待機時間をセット
*/
public AnimationDescription waitBefore(int milli_sec) {
this.wait_before = milli_sec;
return this;
}
/**
* アニメーションのdurationをセット
*/
public AnimationDescription duration(int milli_sec) {
this.anim_duration = milli_sec;
return this;
}
/**
* アニメーション終了後の待機時間をセット
*/
public AnimationDescription waitAfter(int milli_sec) {
this.wait_after = milli_sec;
return this;
}
/**
* アニメーション後の属性補正処理を行わない。
*/
public AnimationDescription dontModify() {
this.exec_modify_flag = false;
return this;
}
// --------- 待機処理を実行 -----------
/**
* 事前待機処理
*/
public void execWaitBefore()
{
if( wait_before > 0 )
{
//FWUtil.d( "事前待機処理を実行します。");
sleepMS(wait_before);
}
}
/**
* 事後待機処理
*/
public void execWaitAfter()
{
if( wait_after > 0 )
{
//FWUtil.d( "事後待機処理を実行します。");
sleepMS(wait_after);
}
}
/**
* アニメーション実行中の待機処理
*/
public void execWaitDuration(int numAnims) {
//FWUtil.d( "duration分の待機処理を実行します。");
sleepMS((int) ( (anim_duration + duration_rest) / (float) numAnims));
}
/**
* 指定されたミリ秒だけスリープ
*/
private void sleepMS(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException ignore) {
}
}
// --------- 具体的なアニメーション -----------
/**
* 具体的なアニメーションの指示を持っているか
*/
public boolean hasDescribedAnimation() {
return ( getDescribedAnimation() != null );
}
/**
* アニメーションのフローを別スレッド上で実行
*/
public void carryAnimationFlowOnOtherThread() {
// 事前待機処理を実行
this.execWaitBefore();
// 全ターゲットViewでアニメ実行
this.kickAnimationsForAllTargetViews();
// 事後待機時間の分だけ,このスレッドは待つ
this.execWaitAfter();
// 全ターゲットViewで事後処理を実行
this.modifyAfterForAllTargetViews();
}
/**
* 全ターゲットViewでアニメーションを実行。
* 前後のフックも含める。
*/
private void kickAnimationsForAllTargetViews()
{
// 1DESC内の全アニメ実行前のハンドラを実行
target_activity.runOnUiThread(new Runnable(){
@Override
public void run() {
beforeAnimate();
root_layout.invalidate();
}
});
// アニメ本体
if( hasDescribedAnimation() )
{
executeAllConcreteAnimations();
}
// 1DESC内の全アニメ実行後のハンドラを実行
target_activity.runOnUiThread(new Runnable(){
@Override
public void run() {
afterAnimate();
root_layout.invalidate();
}
});
}
/**
* 具体的なアニメーション本体を全部実行
*/
private void executeAllConcreteAnimations()
{
// 1つ以上のAnimationが存在するので取得
List<Animation> anims = this.getDescribedAnimation();
// 1desc内の全アニメを実行
int num_anims = anims.size();
for( final Animation anim : anims )
{
executeConcreteOneAnimation( anim, num_anims );
}
}
/**
* 1desc内の1アニメーションを,全Viewで実行
*/
private void executeConcreteOneAnimation( final Animation anim, int num_anims )
{
// Animationにdurationをセット
if( this.anim_duration > 0 )
{
anim.setDuration( (long) (this.anim_duration / (float) num_anims) );
}
// NOTE: アニメーション前後で効果が続くようにしたい(連続実行を前提とするので)
// しかし,下記のメソッドは機能しない。
anim.setFillEnabled(true);
anim.setFillBefore(true);
anim.setFillAfter(true);
// @see http://graphics-geek.blogspot.jp/2011/08/mysterious-behavior-of-fillbefore.html
// "When fillEnabled is true, the value of fillBefore will be taken into account"
// アニメーション終了後の状態を確実に保つためには,終了タイミングで属性をアニメどおりに変化させるしかない。
// @see http://www.androiddiscuss.com/1-android-discuss/75731.html
// http://stackoverflow.com/questions/3345084/how-can-i-animate-a-view-in-android-and-have-it-stay-in-the-new-position-size
// 個々のターゲットViewごとに,具体的なAnimationを実行開始
for( final View v : current_target_views )
{
//FWUtil.d( "アニメーションの開始を登録します。");
// UI上の処理なので,UIスレッドにゆだねる
target_activity.runOnUiThread(new Runnable(){
@Override
public void run() {
//FWUtil.d( "アニメーションを開始します。");
// キック
v.startAnimation(anim);
}
});
}
// UIスレッドでキックしておいたアニメーションはこのまま放任しておく。
// 終了のリスナなどもセットせず,このスレッドからはもう関知しない。
// 1desc内のこのアニメにかかる分だけ待機
this.execWaitDuration( num_anims );
}
// -------- アニメ本体以外の調整など ----------
/**
* 全ターゲットViewで事後処理を実行。補正など
*/
private void modifyAfterForAllTargetViews()
{
for( final View v : current_target_views )
{
if( this.exec_modify_flag ) // 属性変更を禁止されていなければ
{
// 属性変更が主なので,UIスレッドに頼む
final AnimationDescription desc = this;
target_activity.runOnUiThread(new Runnable(){
@Override
public void run() {
// このViewに対する事後処理
//v.invalidate();
desc.modifyAfterAnimation(v);
}
});
}
}
}
/**
* ルートレイアウトにViewを追加
*/
protected void addViewOnStage( View v )
{
root_layout.addView(v, new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT)
);
}
/**
* 1Viewのmarginを調整。
*/
protected void modifyMarginsOfOneView(View v, float x_diff, float y_diff) {
// 現状
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)v.getLayoutParams();
//FWUtil.d(
// "調整前:lp.leftMargin = " + lp.leftMargin + ", lp.topMargin = " + lp.topMargin
// + ", lp.rightMargin = " + lp.rightMargin + ", lp.bottomMargin = " + lp.bottomMargin
//);
// 調整を実行
lp.setMargins((int)(lp.leftMargin + x_diff), (int)(lp.topMargin + y_diff), lp.rightMargin, lp.bottomMargin);
v.setLayoutParams(lp);
//FWUtil.d(
// "調整後:lp.leftMargin = " + lp.leftMargin + ", lp.topMargin = " + lp.topMargin
// + ", lp.rightMargin = " + lp.rightMargin + ", lp.bottomMargin = " + lp.bottomMargin
//);
}
/**
* 基点となるViewの真上に特定のViewが来るようにmarginを調整する。
*/
protected void setPositionAboveOtherView( View target_view, View base_view )
{
// 基点
ViewGroup.MarginLayoutParams lp_base = (ViewGroup.MarginLayoutParams)base_view.getLayoutParams();
// 動かす対象の現在位置
ViewGroup.MarginLayoutParams lp_target = (ViewGroup.MarginLayoutParams)target_view.getLayoutParams();
// 動かす
float x_diff = lp_base.leftMargin - lp_target.leftMargin;
float y_diff = lp_base.topMargin - lp_target.topMargin - target_view.getHeight(); // 自分の高さの分だけ上に来るように
modifyMarginsOfOneView(target_view, x_diff, y_diff);
}
// --------- 内部用 -----------
/**
* 具体的に定義されたアニメーションを内部用に返す。
* nullを返却しうる。
*/
final protected List<Animation> getDescribedAnimation() {
List<Animation> anims = null;
// 単一の場合
Animation anim_single = describe();
if( anim_single != null )
{
anims = new ArrayList<Animation>();
anims.add(anim_single);
}
else // 複数の場合
{
anims = describeAsList();
}
return anims;
}
}