登壇しました
あるあるLT〜スマホアプリ開発エンジニア〜 Vol.4で登壇 ( 資料 )
きっかけとやりたいこと
きっかけ
自分自身、ListViewしかまともに使ってなかったので、AdapterやHolderが何をしていて、なぜ使うのかを理解していないところがありました。LayoutManagerもおまじないだと思ってました。また、ListViewとRecycleViewが一緒に解説に使われているをよく見かけるので、この際に関連するクラスのリファレンスをすべて読むということをしました。この記事は、理解するための用語や各クラスが何をやっているのかを理解してから、実装しています。そのため、始めは解説ばかりになるのでちょっと注意。
やりたいこと
モダンなアプリの中では、よくアニメーションを見かけます。前から実装したいという思いがありました。下のようなものを作ります。(クリックしてタブを開いてみることをお勧めします)
結論から言うと
<android.support.v7.widget.RecyclerView
android:layoutAnimation="@anim/layout_animation_fall_down"
で作成したAnimationファイルを指定するだけでした。先に記事の一番下の「実装する」の欄を見た方がいいかも
MATERIAL DESIGN/Motion/Customization/Sequencingより、
できるもの
必要な知識
- RecyclerView
- RecyclerView.Adapter
- RecyclerView.ViewHolder
- RecyclerView.LayoutManager
- RecyclerView.LayoutManager.Properties
- RecyclerView.LayoutParams
- LayoutAnimationController | Android Developers
ざっと理解
RecycleView
大きなdata setに限られたウィンドウを提供するための柔軟なview。
Nested classesにRecyclerView.Adapter
,RecyclerView.LayoutManager
を持ち、それぞれ、RecycleView#setAdapter(Adapter adapter)
,RecycleView#setLayoutManager(RecyclerView.LayoutManager layout)
があります。また、ViewGroupを継承しているのでViewGroup#setLayoutAnimation(LayoutAnimationController arg0)
用語が沢山。リファレンスに書いてあった。
- Adapter:ビューを提供することを担当。ここで扱うのはRecyclerView.Adapterのサブクラス
- Position:アダプタ内のデータ項目の位置。(相対的?、絶対的?positionに気を付けること)
- Index:getChildAt(int)の呼び出しで使用されている添付子ビューのインデックス。レイアウト相対位置。
- Binding:アダプタ内の位置に対応するデータを表示するために子ビューを準備するプロセス
役割
Viewのbindingが一度に全てのHolderに行われないように制御する。必要なHolderをロードする。こうするとパフォーマンスが良い。
RecycleViewがデータセットの変更検出を担当しているのでLayoutManagerはアニメーションを担うだけでいい
RecyclerView.Adapter
継承関係を見てみます。
メソッドにViewが必要な時に内部的に呼び出されるbindViewHolder(VH holder, int position)
が定義されています。
adapterのデータに変更があった場合はnotifyDataSetChanged()が呼ばれます。(データセットが変更されたことを通知します。)
またRecycleView
のリファレンスに...
データ変更イベント(data change events)には
- 項目変更(アイテムのデータが更新) item changes
- 構造変更(アイテムが挿入、削除、移動) structural changes がある。
どちらでも、notifyDataSetChanged()
で呼び出される。
ようは、itemの更新、挿入、削除、移動の実装を持っているクラスってこと
よく使うBaseAdapterは継承関係に無かった(知らなかった)
RecyclerView.ViewHolder
ViewHolderはRecyclerView内についてのitem viewとmetadataのセット(ようはviewとdataのセット)を持っている。
リファレンスから
RecyclerView.LayoutParams
はRecyclerView.LayoutManager
に属します。ViewHolders
はAdapter
に属します。
とあった。複数のHolderがAdapterに属するとのこと。
RecyclerView.LayoutManager
リファレンスの要約
LayoutManagerは、RecyclerView内のitem viewを測定(measuring)および配置(positioning)し、ユーザーに表示されなくなったitem viewをいつリサイクルするかについてのポリシーを決定する。 LayoutManagerを変更することによって、RecyclerViewを使用して、標準の垂直スクロール(vertically scrolling)、均一グリッド(uniform grid)、スタガードグリッド(staggered grids)、水平スクロール(horizontally scrolling)などを実装できます。LayoutManagerはいくつか用途に合わせ、クラスが用意してあります。
もっと詳しく
RecyclerView.LayoutManager.Properties
LayoutManagerのPropertyで、以下のようなフィールドを持っている。フィールドはRecyclerView.LayoutManager
から操作される。
型 | 変数名 | 意味 |
---|---|---|
int | orientation | オリエンテーション、方向の事 |
boolean | reverseLayout | レイアウトを逆にすることが可能かどうか |
int | spanCount | HTMLで「ひとつの範囲として定義する」の意味 恐らく、ブロック(CardViewも考慮して)の数 |
boolean | stackFromEnd | 最後からスタックするかどうか RecyclerViewを下から表示するにはstackFromEnd = trueを入れる |
RecyclerView.LayoutParams
RecyclerViewのitem Viewに対するParams
ViewGroup.LayoutParams
RecyclerView.LayoutParams
のスーパークラスになるもの。
フィールド
field | Description |
---|---|
int | viewの高幅 |
LayoutAnimationController.AnimationParameters |
layoutAnimationParameters レイアウトをアニメートするために使用されます。 |
int | viewの横幅 |
LayoutAnimationController.AnimationParameters
があった。これでitem viewをfade-inさせることができるのでは?
アニメーションを付けるには?
ここで考えます。目的のアニメーションをさせるには何を使えばよいでしょう。
登場人物はこれですね
- RecyclerView
- RecyclerView.Adapter
- RecyclerView.ViewHolder:RecyclerView内のitem viewとmetadataのセット
- RecyclerView.LayoutManager:item viewのサイズを測定&配置、リサイクルの有無を決める
- RecyclerView.LayoutManager.Properties:プロパティである。レイアウトの方向を決める。orientation
- RecyclerView.LayoutParams:Paramsである。縦幅と横幅の数値が定義されている。ビューのデータが変更されたかのフラグがある。
setAnimationはどこだろう?
android.view.View
にあった。setAnimation(Animation arg0)
なのでRecycleView.setAnimation(Animation arg0)
になる。しかし、これだとRecycleViewが回転、移動するアニメーションしか作れない。(Holderをfade-inさせるのではない)
LayoutAnimationController.AnimationParametersはどこだろう?
因みに、ViewGroup.LayoutParams
ORRecycleView.LayoutParams
にはLayoutAnimationController.AnimationParameters
型が必要。ならばLayoutAnimation
をセットするメソッドが何処かにあるはずだ。
ViewGroup#setLayoutAnimation(LayoutAnimationController controller)
があった。つまり、継承関係にあるRecycleViewで使える。
LayoutAnimationController
では、LayoutAnimationController
とはなんのクラスだろうか?
android.view.animation | Android Developers
LayoutAnimationControllerは、レイアウトの子(layout's children)、またはビューグループの子(view group's children)をアニメーション化するために使用
これだ。
使えそうなもの
setAnimation(Animation animation)
setAnimation (Context context, int resourceID)
setDelay (float delay)
setInterpolator(Interpolator interpolator)
setInterpolator(Context context, int resourceID)
setOrder(int order)
start()
setInterpolator(Context context, int resourceID)
を使うことにします。
ViewGroupは2種類
- ViewGroup | Android Developers
- ViewGroup.LayoutParams | Android Developers
実装する
参考記事
英語の記事ですが、用語ややっていることが分かってしまえば簡単です。
Enter animation using RecyclerView and LayoutAnimation Part 1: Lists
自分のGithub
fade-inの実装
RecycleView
RcycleViewはxmlでandroid:layoutAnimation
attributeがあるので、これを使用します。(動的にlayoutAnimationを指定したいなら、ViewGroup#setLayoutAnimation()
を使う)
まず、更新ボタンとRecycleViewをVerticalに設置します。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="更新"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:id="@+id/load"
app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="8dp"
app:layout_constraintStart_toStartOf="parent" android:layout_marginStart="8dp"
android:layout_marginTop="8dp" app:layout_constraintTop_toTopOf="parent"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layoutAnimation="@anim/layout_animation_fall_down"
android:layout_height="0dp" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/load" app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="1.0">
</android.support.v7.widget.RecyclerView>
</android.support.constraint.ConstraintLayout>
</layout>
anim
次にanim Directoryを作成します。
res>new>Android Resource Directotyを選択してanim Directoryを作成します。
そのディレクトリの中にアニメーションのresourceファイルを作っていくことになります。
気を取り直して、anim Directoryにlayout_animation_fall_down.xml
を作成します。
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation
xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@android:anim/fade_in"
android:delay="15%"
android:animationOrder="normal"
/>
android:delayは各HolderのAnimationをどのくらい遅延させるかです。android:animationOrderはrandomを指定すると、ランダムに各HolderのAnimationが開始されます。詳しくはリファレンスに書いてあります。
android:animationで各HolderのAnimationの振る舞いを指定できます。ここでは、Android標準のfade_inリソースを使っています。
更新する
onClickListenerで以下のメソッドを呼び出します。LayoutManagerはVerticalにしたいのでRecyclerView.LayoutManager
のサブクラスであるLinearLayoutManager
を使います。
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding =
DataBindingUtil.inflate(inflater, R.layout.fragment_feed_list1, container, false)
val list =
listOf<String>("a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k","a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k","a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k")
// LayoutManagerの設定
val layoutManager = LinearLayoutManager(context)
binding.recyclerview.layoutManager = layoutManager
binding.recyclerview.adapter = DataAdapter(list)
binding.load.setOnClickListener { runLayoutAnimation(binding.recyclerview) }
return binding.root
}
private fun runLayoutAnimation(recyclerView: RecyclerView) {
val context = recyclerView.context
val controller = AnimationUtils.loadLayoutAnimation(context, R.anim.layout_animation_fall_down)
recyclerView.layoutAnimation = controller
recyclerView.adapter!!.notifyDataSetChanged()
recyclerView.scheduleLayoutAnimation()
}
完成です。
fade_in.xml
material designにそった実装ですね。
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@interpolator/decelerate_quad"
android:fromAlpha="0.0" android:toAlpha="1.0"
android:duration="@android:integer/config_longAnimTime" />
interpolatorの種類はこちらのページ
カスタムしてみる
fadeをしないで下から上にスライドするitemのAnimation
<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation
xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/item_animation_fall_down" //カスタム
android:delay="15%"
android:animationOrder="normal"
/>
itemのanimetionを作る
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000">
<translate
android:fromYDelta="+100%p"
android:toYDelta="0"
android:interpolator="@android:anim/decelerate_interpolator"
/>
</set>
android:duration="1000"
でAnimationの長さを決定します。android:fromYDelta
はpをつけるとparentサイズの割合になる。マイナスを指定すると、上から下に動きます。
その他のAttributeはリファレンス
参考資料
LayoutManagerの応用方法についての資料です。とても為になります。
最後に
クラスをすべて読み切ったが、丸々4日ぐらいかかったし、コストが高いと思った。早めにsetLayoutAnimation
に気づけばいいのだが..。次は、このページのOscillationをやってみたいですね。