LoginSignup
10

More than 3 years have passed since last update.

RecycleViewにfade-inを実装する(sequencing)

Last updated at Posted at 2019-03-17

登壇しました

あるある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より、

kz8sf-ckb07.gif

できるもの

mobizen_20190306_233640_2.gif

必要な知識

ざっと理解

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.LayoutParamsRecyclerView.LayoutManagerに属します。ViewHoldersAdapterに属します。

とあった。複数の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.LayoutParamsORRecycleView.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

Test_RecycleViewAnimation

fade-inの実装

RecycleView

RcycleViewはxmlでandroid:layoutAnimationattributeがあるので、これを使用します。(動的に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を作成します。

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()
    }

完成です。

mobizen_20190306_233640_2.gif

fade_in.xml

material designにそった実装ですね。

fade_in.xml
<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

layout_animation_fall_down.xml
<?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を作る

item_animation_fall_down.xml
<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サイズの割合になる。マイナスを指定すると、上から下に動きます。

0b66b4ee4ffb89e90403d9960ed15b9a.gif

その他のAttributeはリファレンス

参考資料

LayoutManagerの応用方法についての資料です。とても為になります。

DroidKaigiアプリのタイムテーブルを支える技術

Shibuya.apk #32でのLT動画

最後に

クラスをすべて読み切ったが、丸々4日ぐらいかかったし、コストが高いと思った。早めにsetLayoutAnimationに気づけばいいのだが..。次は、このページのOscillationをやってみたいですね。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10