RecyclerViewでアイテムの変更(Change/Insert/Move/Remove)を行うと、変更される様子がアニメーション化されています。
これはデフォルトでアイテムの変更アニメーションが組み込まれているためです。
デフォルトは単純なアニメーションですが、「ある」と「ない」の違いは歴然で、アニメーションのある方が高価なアプリケーションに見えます。
GUI(Graphical User Interface)が主体の携帯端末にとって、利用者に対するアプリの見せ方は重要です。高価に見えた方が使ってもらえる可能性が高くなります。
上記のことから、アプリの機能に関係なくても、ちょっとした動きをアニメーション化するメリットがあります。
2回にわたりアイテムの変更アニメーションについてまとめてみました。
- アイテムの変更アニメーション(DefaultItemAnimator、デフォルト)
- アイテムの変更アニメーション(SimpleItemAnimatorの継承、カスタム)
今回は第1回目「ItemAnimator、デフォルト」編です。デフォルト変更アニメーションの動作について説明します。
目次
デフォルト変更アニメーション(DefaultItemAnimator)
アイテムの変更アニメーションはItemAnimatorクラスが行っています。
(※ItemAnimatorは抽象クラスなので継承先のクラスへ動作が実装されるている)
ItemAnimatorのインスタンスをRecyclerViewの中で格納しているのがmItemAnimatorフィールドです。
このmItemAnimatorは、RecyclerViewのインスタンス作成時に、DefaultItemAnimatorクラスのインスタンスで初期化されます。
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2, NestedScrollingChild3 { ... ItemAnimator mItemAnimator = new DefaultItemAnimator(); ... }
つまり、DefaultItemAnimatorが変更アニメーションのデフォルトです。
デフォルト変更アニメーションの動作
デフォルト変更アニメーション(DefaultItemAnimator)は次に示すような単純なものです。
(同時にフェードイン・アウト)
(移動で空欄を作り⇒フェードイン)
(同時に移動)
(フェードアウト⇒移動で空欄を埋める)
デフォルト変更アニメーションの仕組み
前述のとおり、アイテムの変更アニメーションはItemAnimatorクラスが行っています。
ただし、アニメーションはItemAnimatorの単独ではなく、RecyclerViewとItemAnimatorの連携で成り立っています。
アニメーション実行の手順
変更アニメーションは次のような手順で実行されています。
- (0)アイテム変更の通知を受け取る
- (1)アイテム変更の内容をアニメーションへ分解
- (2)アニメーションのリクエストを登録、実行を要求
- (3)画面リフレッシュタイミングでアニメーション実行
ItemAnimatorは抽象クラスなので、継承先のクラスでボックス内の動作を実装しなければなりません。
(0)アイテム変更の通知を受け取る
RecyclerViewでアイテムの変更を行う場合、アイテムのデータを変更した後に、変更内容に合った通知(notifyItemXXXの呼出:XXXは変更内容)を行うことになっています。
notifyItemXXXが呼び出されると、RecyclerViewDataObserver内のメソッド(onItemXXX:xxxは変更内容)が実行されます。
onItemXXXメソッドはRecyclerViewへ変更内容を通達し、triggerUpdateProcessorメソッドを実行します。
(1)アイテム変更の内容をアニメーションへ分解
triggerUpdateProcessorメソッドの仕事は、アイテム変更の内容をアイテム毎のアニメーションへ分解することです。
図は変更内容「ポジション2を削除」をアニメーションへ分解する例です。
- Item2を消す –> animateDisappearanceを呼び出し
- Item3を上に移動 –> animatePersistenceを呼び出し
- Item4を上に移動 –> animatePersistenceを呼び出し
- Item5を上に移動 –> animateAppearanceを呼び出し
分解されたアニメーションに対応したメソッドを呼び出します。
(2)アニメーションのリクエストを登録、実行を要求
RecyclerViewから呼出される以下のメソッドは抽象メソッドです。
public abstract static class ItemAnimator { ... public abstract boolean animateDisappearance( ViewHolder viewHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo); public abstract boolean animateAppearance( ViewHolder viewHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo); public abstract boolean animatePersistence( ViewHolder viewHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo); public abstract boolean animateChange( ViewHolder oldHolder,ViewHolder newHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo); ... }
メソッドの中でアニメーションのリクエストをリストへ登録する実装が求められます。
リクエストのフォーマットやリストの管理方法は実装に任せられています。
メソッド | 呼出の場面 | アニメーション動作(リクエスト内容) |
---|---|---|
animateDisappearance | アイテムが消去 | ・その場で消える(Remove) ・表示範囲外へ移動(Move) |
animateAppearance | アイテムが出現 | ・その場で現れる(Add) ・表示範囲内へ移動(Move) |
animatePersistence | アイテムの表示継続 | ・表示範囲内で移動(Move) |
animateChange | アイテムの交換 | ・旧:その場で消える(Change) ・新:その場で現れる(Change) |
また、メソッドの戻り値はbooleanになっていて、true/falseは次のような意味を持ちます。
- true : runPendingAnimationsの呼出(アニメーションの実行)を要求する
- false: runPendingAnimationsの呼出(アニメーションの実行)を要求しない
(3)画面リフレッシュタイミングでアニメーション実行
(2)の4つの抽象メソッドからtrueが返されると、View#postOnAnimationを使ってrunPendingAnimationsメソッドの呼び出しがスケジュールされます。
ここで重要なのがView#postOnAnimationです。
Andoridはテレビのように一定の間隔で画面を更新(リフレッシュ)しています。その更新の頻度をフレームレート[fps](1秒間に更新される画面数)と言い、Androidは60[fps]を目標に動いています。
「目標」と書いたのは60[fps]が達成できない場合もあるからです。リフレッシュはUI(メイン)スレッドが制御しているので、UIスレッドが他の重い処理(長時間の処理)を行っている場合などに遅れるからです。
View#postOnAnimationは次のリフレッシュのタイミングでRunnable#runメソッドを実行します。
runPendingAnimationsは抽象メソッドです。メソッドの中でアニメーションを実行する実装が求められます。
デフォルト変更アニメーションの継承関係
DefaultItemAnimatorはItemAnimator抽象クラスを直接継承していません。
間にSimpleItemAnimator抽象クラスを挿みます。
SimpleItemAnimatorの役割は、アニメーションをリクエストに変換することです。
また、後処理の窓口になるdispatchXXXFinishedメソッド(XXXはリクエスト名)が追加されています。
カスタムItemAnimatorはSimpleItemAnimatorを継承して作成するのが効率的です。
変更アニメーションの変更
アイテムの変更アニメーションをデフォルトから別のものへ変更が可能です。。
そのためのRecyclerView#setItemAnimatorメソッドが用意されています。
... public void setItemAnimator(@Nullable ItemAnimator animator) { if (mItemAnimator != null) { mItemAnimator.endAnimations(); mItemAnimator.setListener(null); } mItemAnimator = animator; if (mItemAnimator != null) { mItemAnimator.setListener(mItemAnimatorListener); } } ...
... rcySample.itemAnimator = CustomItemAnimator() ...
当然、指定するクラスはItemAnimator(またはSimpleItemAnimator)クラスを継承したものです。
関連記事: