何がしたいか
・Viewをタップしたときにアニメーションに合わせてExpandしたりCollapseしたい
ViewのVisibiltyを切り替えればExpandしたりCollapseできるけど、アニメーションは付けられない。
View#animate()とかで開閉するアニメーションをしても上手く行きません。詳しくは後述。
結論
・ViewをリサイズするためにAnimationクラスを継承したResizeAnimationを作る
参考:
http://stackoverflow.com/questions/8063466/how-to-expand-a-layout-height-with-animation
animate()やstartAnimation()でアニメーションしたとき
実はViewのサイズや位置情報は変更されてないのです。
だから、Layoutをこんな風にして
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#ef5350"
android:id="@+id/content">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="タイトル:えきすぱんどView"/>
<TextView
android:id="@+id/expandable_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/body"
android:layout_below="@+id/text_view"
android:layout_centerHorizontal="true"/>
</RelativeLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="動かないよー"
android:id="@+id/button"
android:layout_gravity="center_horizontal"/>
</LinearLayout>
んでもって、
public class MainActivity extends AppCompatActivity {
private TextView expandableView;
private View content;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.animate_layout);
content = findViewById(R.id.content);
expandableView = (TextView) findViewById(R.id.expandable_view);
content.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 開くようなアニメーション
if(expandableView.getVisibility() != View.VISIBLE) {
expandableView.animate().alphaBy(0).alpha(1).translationYBy(-10).translationY(0).withStartAction(new TimerTask() {
@Override
public void run() {
// アニメーションが始まる前にViewをVISIBLEにする
expandableView.setVisibility(View.VISIBLE);
}
});
}
// 開じるようなアニメーション
else {
expandableView.animate().alpha(0).translationY(-10).withEndAction(new TimerTask(){
@Override
public void run() {
// アニメーションが終わったらViewをGONEにする
expandableView.setVisibility(View.GONE);
}
});
}
}
});
}
こんな風にしてButtonを押すと開閉するアニメーションを作る。
上手く行きませんね。
今度は上下に移動するようなアニメーションも試してみます。
これもダメ。
理由は先述の通りアニメーションは見かけ上が変化しているだけで
実際のViewのサイズ、位置情報に変化がないから。
これをViewの実サイズもリサイズして冒頭のアニメーションを実現したい。
ResizeAnimationを作る
ViewのサイズをリサイズするようにAnimationクラスを継承してカスタムします。
以下のようにします。
public class ResizeAnimation extends Animation {
final int addHeight;
View view;
int startHeight;
public ResizeAnimation(View view, int addHeight, int startHeight) {
this.view = view;
this.addHeight = addHeight;
this.startHeight = startHeight;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
int newHeight = (int) (startHeight + addHeight * interpolatedTime);
view.getLayoutParams().height = newHeight;
view.requestLayout();
}
@Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
}
@Override
public boolean willChangeBounds() {
return true;
}
}
ResizeAnimationのコンストラクタの
第一引数はアニメーションさせたいView
第二引数はアニメーション後に追加する高さ
第三引数はアニメーション開始時の高さ
を取ります。
例えば、アニメーション後に高さを0にしたい場合は
第二引数は -startHeightになる。
これをさっきのMainActivityで以下のように使う。
Animationクラスを継承しているので、Interpolatorとかももちろん設定できる。
setDurationを設定しないと上手くいかないので必ず設定すること。
(あるいはコンストラクタで適当なデフォルト値を入れるようにする)
今回はsetDurationを300に設定。
public class MainActivity extends AppCompatActivity {
private TextView expandableView;
private int originalHeight;
private View content;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.animate_layout);
content = findViewById(R.id.content);
expandableView = (TextView) findViewById(R.id.expandable_view);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
// ExpandするViewの元のサイズを保持する
originalHeight = expandableView.getHeight();
// ビューを閉じるアニメーションを生成
final ResizeAnimation collapseAnimation = new ResizeAnimation(expandableView, -expandableView.getHeight(), expandableView.getHeight());
collapseAnimation.setDuration(300);
// ビューを開くアニメーションを生成
final ResizeAnimation expandAnimation = new ResizeAnimation(expandableView, originalHeight, 0);
expandAnimation.setDuration(300);
content.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
expandableView.clearAnimation();
if(expandableView.getHeight() > 0) {
expandableView.startAnimation(collapseAnimation);
} else{
expandableView.startAnimation(expandAnimation);
}
}
});
}
}
ここで気をつけないといけないのは
onCreateの中ではViewのサイズ情報は取得できないこと。(getHeightとかしても0になる)
onWindowFocusChangedの中で呼んであげればちゃんと取得することができる。
アニメーションするとViewのサイズが変わってしまうので、
アニメーションする前に元のサイズを保持しておく必要がある。
実際にできるかどうか試してみよう。
ちゃんと冒頭のようになった!
今回の例はResizeAnimationに高さしか指定しないコンストラクタを作成したけど、
もちろん横幅を指定したり、座標を指定するコンストラクタを作れば、高さと同じように横幅を変えたり、位置を変えたりもできる。