RecyclerView:アイテムのスナップ(SnapHelper)

投稿日:  更新日:

RecyclerViewはアイテムをスクロールさせて隠れたアイテムを表示できます。

スクロールを止める位置は任意です。

任意であるがゆえに、止めた位置によってはアイテムの一部が欠けてしまうこともあります。

携帯端末の画面は狭いので、効率よくコンテンツの表示を行いたいとアプリ開発者は考えます。

アイテムの一部が欠けてしまうことは、効率が良いとは言えません。

このような問題を解決するために、アイテムのスナップをRecyclerViewへ追加できます。

アイテムのスナップを追加する方法を紹介します。

スポンサーリンク

スナップとは

「アイテムのスナップ」とは簡単に言えば「アイテムへ配置条件を設ける」ことです。

例えば、下図は「RecyclerViewの中央にアイテムの上辺を配置する」という条件を持たせた場合です。

条件から外れた配置は出来ません。条件を満たす配置へ吸い付くように移動します。

先にも書きましたが、狭い携帯端末の画面を効率よく使うための、1つの手段です。

アイテムが画像であったり、文字であったり、アイテムの内容によって「効率がよい」の意味が違ってくるので、一概には言えませんが…

スポンサーリンク

RecyclerViewの中央へスナップ(既成クラス)

recyclerview APIにLinearSnapHelerクラスが用意されています。

LinearSnapHelperはスナップをRecyclerViewへ追加します。名前の通り、LinearLayoutManager向けです。

LinearSnapHelperの実装

LinearSnapHelperのアイテムの配置条件は「RecyclerViewの中央に、アイテムの中央を配置する」です。

        ...
		lateinit var rcySample: RecyclerView
        lateinit var snap: SnapHelper
		
		...
        val _adapter = ListAdapter(_items)
        val _manager = LinearLayoutManager(this@MainActivity)
        rcySample = findViewById(R.id.rcySample)
        rcySample.adapter = _adapter
        rcySample.layoutManager = _manager
		
        ...
        snap = LinearSnapHelper()
        snap.attachToRecyclerView(rcySample)
		...
        ...
		lateinit var rcySample: RecyclerView
        lateinit var snap: SnapHelper
		
		...
        val _adapter = ListAdapter(_items)
        val _manager = LinearLayoutManager(this@MainActivity, LinearLayoutManager.HORIZONTAL, false)
        rcySample = findViewById(R.id.rcySample)
        rcySample.adapter = _adapter
        rcySample.layoutManager = _manager
		
        ...
        snap = LinearSnapHelper()
        snap.attachToRecyclerView(rcySample)
		...

実装は簡単でSnapHelper#attachToRecyclerViewメソッドでRecyclerViewへ関連付けるだけです。※LinearSnapHelperはSnapHelperを継承

VERTICALとHORIZONTALの判定はLinearSnapHelperが行ってくれます。

LinearSnapHelperの仕組み

期待したスナップへカスタマイズするために仕組みの理解が必要です。

※なかなかコード読みが難しく、この章は間違いが多いかもしれません。

スナップ動作の手順

初めに、全体的な動作を説明します。スナップは次の3つの手順で成り立っています。

  • (1)最終的なポジションの算出
  • (2)フリング(Fling)によるスクロール
  • (3)配置条件合わせのスクロール

スナップ動作


 (1)最終的なポジションの算出 

フリング直後に、現在のポジション(currentPosition)とスクロール量の見積もり(deltaJump)を足し合わせて、最終的なポジション(targetPos)を算出します。


 (2)フリングによるスクロール 

フリングによりスクロールが起動されます。

スクロール量はフリングの速度(velocityX/Y)で決まります。速度が早ければスクロール量は多くなり、遅ければ少なくなります。

スクロール後の停止位置は(1)で算出した最終的なポジションよりも少し手前です。
※なぜ、手前になるのか?理由がプログラムコードから読み取れませんでした。よって、事象の確認のみです。

スクロール後の停止位置と最終的なポジションの差が(3)配置条件合わせのスクロール距離になります。


 (3)配置条件合わせのスクロール 

最終的なポジションまでスクロールを行います。スクロールを行う中でスナップの配置条件を合わせます。

スクロールというとアイテムが上下に流れて行くという描写になります。しかし、プログラム的にはRecyclerViewの枠がアイテムのリスト上を移動していくイメージの方が正しいです。

SnapHelperを継承

LinearSnapHelperはSnapHelper抽象クラスを継承して作られています。

実装が必要な抽象メソッドは3つです。

これらのメソッドは手順(1)~(3)の動作を決めます。期待したスナップが欲しい場合にカスタマイズが必要なメソッドです。


 findSnapView 

RecycylerViewの中からスナップ対象のアイテム(View)を見つけて、返します。

LinearSnapHelperの場合は、RecyclerViewの中央へアイテムの中央を配置する条件になっているので、両者の中央が最も近いアイテムを返します。

    @Override
    public View findSnapView(RecyclerView.LayoutManager layoutManager) {
        if (layoutManager.canScrollVertically()) {
            return findCenterView(layoutManager, getVerticalHelper(layoutManager));
        } else if (layoutManager.canScrollHorizontally()) {
            return findCenterView(layoutManager, getHorizontalHelper(layoutManager));
        }
        return null;
    }
	
	...
	
	@Nullable
    private View findCenterView(RecyclerView.LayoutManager layoutManager,
            OrientationHelper helper) {
        int childCount = layoutManager.getChildCount();
        if (childCount == 0) {
            return null;
        }

        View closestChild = null;
        final int center = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
        int absClosest = Integer.MAX_VALUE;

        for (int i = 0; i < childCount; i++) {
            final View child = layoutManager.getChildAt(i);
            int childCenter = helper.getDecoratedStart(child)
                    + (helper.getDecoratedMeasurement(child) / 2);
            int absDistance = Math.abs(childCenter - center);

            /** if child center is closer than previous closest, set it as closest  **/
            if (absDistance < absClosest) {
                absClosest = absDistance;
                closestChild = child;
            }
        }
        return closestChild;
    }


 findTargetSnapPosition 

手順(1)の最終的なポジションを返します。

このメソッドはフリングのイベントの直後に呼び出されます。

フリングの速度(velocityX/Y)からスクロール量(deltaJump:ポジションの移動数)を見積もって、現在のポジション(currentPosition)へ加算した値を最終ポジション(targetPos)として返します。

現在のポジションはfindSnapViewメソッド(前述)を使って求めています。

@Override
    public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,
            int velocityY) {
        ...

        final View currentView = findSnapView(layoutManager);
        if (currentView == null) {
            return RecyclerView.NO_POSITION;
        }

        final int currentPosition = layoutManager.getPosition(currentView);
        if (currentPosition == RecyclerView.NO_POSITION) {
            return RecyclerView.NO_POSITION;
        }

        ... (HORIZONTALの処理は省略)

        if (layoutManager.canScrollVertically()) {
            vDeltaJump = estimateNextPositionDiffForFling(layoutManager,
                    getVerticalHelper(layoutManager), 0, velocityY);
            if (vectorForEnd.y < 0) {
                vDeltaJump = -vDeltaJump;
            }
        } else {
            vDeltaJump = 0;
        }

        int deltaJump = layoutManager.canScrollVertically() ? vDeltaJump : hDeltaJump;
        if (deltaJump == 0) {
            return RecyclerView.NO_POSITION;
        }

        int targetPos = currentPosition + deltaJump;
        if (targetPos < 0) {
            targetPos = 0;
        }
        if (targetPos >= itemCount) {
            targetPos = itemCount - 1;
        }
        return targetPos;
    }


 calculateDistanceToFinalSnap 

手順(3)のスクロール距離を返します。

LinearSnapHelperの場合は、RecyclerViewの中央とスナップ対象アイテムの中央との差を返すことになります。

    @Override
    public int[] calculateDistanceToFinalSnap(
            @NonNull RecyclerView.LayoutManager layoutManager, @NonNull View targetView) {
        int[] out = new int[2];
		
        ... (HORIZONTALの処理は省略)

        if (layoutManager.canScrollVertically()) {
            out[1] = distanceToCenter(layoutManager, targetView,
                    getVerticalHelper(layoutManager));
        } else {
            out[1] = 0;
        }
        return out;
    }
	
	...
	
	private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager,
            @NonNull View targetView, OrientationHelper helper) {
        final int childCenter = helper.getDecoratedStart(targetView)
                + (helper.getDecoratedMeasurement(targetView) / 2);
        final int containerCenter = helper.getStartAfterPadding() + helper.getTotalSpace() / 2;
        return childCenter - containerCenter;
    }

RecyclerViewへ関連付ける

アイテムのスナップはRecyclerViewへLinearSnapHelperを関連付ける(SnapHelper#attachToRecyclerViewメソッド)ことで動作します。

関連付けで行われていることは次の通りです。

    public void attachToRecyclerView(@Nullable RecyclerView recyclerView)
            throws IllegalStateException {
        if (mRecyclerView == recyclerView) {
            return; // nothing to do
        }
        if (mRecyclerView != null) {
            destroyCallbacks();
        }
        mRecyclerView = recyclerView;
        if (mRecyclerView != null) {
            setupCallbacks();
            mGravityScroller = new Scroller(mRecyclerView.getContext(),
                    new DecelerateInterpolator());
            snapToTargetExistingView();
        }
    }

    ...
    private void setupCallbacks() throws IllegalStateException {
        if (mRecyclerView.getOnFlingListener() != null) {
            throw new IllegalStateException("An instance of OnFlingListener already set.");
        }
        mRecyclerView.addOnScrollListener(mScrollListener);
        mRecyclerView.setOnFlingListener(this);
    }


 RecyclerViewへOnScrollListenerの追加 

手順(1)~(3)の実行タイミングを作っています。


 RecyclerViewへOnFlingListenerの設定 

RecyclerViewのフリング処理をSnapHelperのフリング処理へ置き換えています。

RecyclerViewはフリング処理を持っています。

フリングが発行されると、デフォルトのアニメーション(Interpolator )でスクロールを実行するという処理です。

デフォルトのアニメーションは後半に向かってスクロールの速度が落ちて行きます。

このアニメーションだと、手順(2)と(3)のスクロールの繋ぎが滑らかになりません。

これを回避するためにフリング処理を置き換えていると思われます。

ちなみに、上記のデフォルトのアニメーションはRecyclerViewの中で変更が禁止(final属性を持つ)されています。

スポンサーリンク

RecyclerViewの中央へスナップ(自作クラス)

LinearSnapHelperはSnapHelper抽象クラスを継承して作成されています。

このSnapHelperは抽象度が大きいので、もう少し抽象度を小さくしたクラスを作成しました。

ベースの抽象クラス(LinearSnap)

抽象度を小さくしたので使用条件が限定されます。

LinearSnapはLinearLayoutManager限定で、スナップ先はRecyclerViewの中央のみです。

また、VERTICALとHORIZONTALの判定を組み込みました。実装時に記述が減らせます。

抽象メソッドはdistanceToCenterとfindCenterViewの2つです。

abstract class LinearSnap : SnapHelper() {

    // 最終的なスナップに必要なスクロール範囲と向きを返す
    override fun calculateDistanceToFinalSnap(
        manager: RecyclerView.LayoutManager, targetView: View
    ): IntArray {
        val _hDistance =
            if(manager.canScrollHorizontally()) {
                val _helper = OrientationHelper.createHorizontalHelper(manager)
                distanceToCenter(targetView, _helper)
            } else 0
        val _vDistance =
            if(manager.canScrollVertically()) {
                val _helper = OrientationHelper.createVerticalHelper(manager)
                distanceToCenter(targetView, _helper)
            } else 0

        return intArrayOf(_hDistance, _vDistance)
    }

    // 最終的なポジションを返す
    override fun findTargetSnapPosition(
        manager: RecyclerView.LayoutManager, velocityX: Int, velocityY: Int
    ): Int {
        if(manager !is RecyclerView.SmoothScroller.ScrollVectorProvider)
            return RecyclerView.NO_POSITION
        if(manager.itemCount == 0)
            return RecyclerView.NO_POSITION

        // 現在の表示におけるSnap対象のView(アイテム)
        val _currentView = findSnapView(manager)
        if(_currentView == null)
            return RecyclerView.NO_POSITION
        val _cureentPosition = manager.getPosition(_currentView)
        if(_cureentPosition == RecyclerView.NO_POSITION)
            return RecyclerView.NO_POSITION

        // 末尾へ向かう時のスクロールの向き(昇順であれば+)
        val _vectorForEnd = 
		    manager.computeScrollVectorForPosition(manager.itemCount)
        if(_vectorForEnd == null)
            return RecyclerView.NO_POSITION

        // フリングでScrollされるポジション数(実際よりも多くなる、理由不明)
        val _hDeltaJump = if(manager.canScrollHorizontally()) {
            val _deltajump = estimateNextPositionDiffForFling(
                manager,
                OrientationHelper.createHorizontalHelper(manager),
                velocityX, 0
            )
            if(_vectorForEnd.x < 0.0f) (- _deltajump) else _deltajump
        } else 0
        val _vDeltaJump = if(manager.canScrollVertically()) {
            val _deltajump = estimateNextPositionDiffForFling(
                manager,
                OrientationHelper.createVerticalHelper(manager),
                0, velocityY
            )
            if(_vectorForEnd.y < 0.0f) (- _deltajump) else _deltajump
        } else 0
        val _deltaJump = if(manager.canScrollVertically()) _vDeltaJump
                       else _hDeltaJump
        if(_deltaJump == 0)
            return RecyclerView.NO_POSITION

        // フリング後にSnap対象になるポジション
        var _targetPos = _cureentPosition + _deltaJump
        _targetPos = if(_targetPos < 0) 0 else _targetPos
        _targetPos = if(_targetPos >= manager.itemCount) manager.itemCount - 1
                     else _targetPos

        return _targetPos
    }

    // スナップ対象のアイテム(のView)を選択し、返す
    override fun findSnapView(manager: RecyclerView.LayoutManager): View? {
        if(manager.canScrollHorizontally()) {
            val _view = findCenterView(
                manager, OrientationHelper.createHorizontalHelper(manager))
            return _view
        }
        if(manager.canScrollVertically()) {
            val _view = findCenterView(
                manager, OrientationHelper.createVerticalHelper(manager))
            return _view
        }

        return null
    }

    // ----- LinearSnapHelperのアルゴリズム見直し ---------------------

    // フリング(Fling)によりスクロールされるアイテム数を仮見積もりする
    //
    // 前提条件:
    //  ・全てのアイテムViewは高さが等しい
    private fun estimateNextPositionDiffForFling(
        manager: RecyclerView.LayoutManager, helper: OrientationHelper,
        velocityX: Int, velocityY: Int
    ): Int {
        val _distances = calculateScrollDistance(velocityX, velocityY)
                                            // ↑ SnapHelperのメソッド ↑
        val _view = manager.getChildAt(0)   // 代表としてindex:0を使う
        val _distancePerChild = helper.getDecoratedMeasurement(_view)

        val _distance = if(abs(_distances[0]) > abs(_distances[1]))
            _distances[0]
        else
            _distances[1]

        return round(_distance.toDouble() / _distancePerChild.toDouble())
            .toInt()
    }

    // ----- スナップ条件に併せて作成 ---------------------------------

    // 中央(スナップ先)までの距離と向きを返す.
    abstract fun distanceToCenter(targetView: View, helper: OrientationHelper)
    : Int

    // 中央(スナップ先)にあるアイテム(View)を返す.
    abstract fun findCenterView(
        manager: RecyclerView.LayoutManager, helper: OrientationHelper)
    : View?
}

関連付け方法の改善

ベースの抽象クラスの作成に合わせて関連付け方法も改善します。

RecyclerViewに関連付けを行うメソッドを持たせるようにしました。Kotlinの拡張関数を用いています。

こちらの方が主役(RecyclerView)と脇役(SnapHelper)の関係が合っていると思います。

abstract class LinearSnap : SnapHelper() {
    ...
}

// ==================================================================
// RecyclerView拡張関数
// ==================================================================

fun RecyclerView.attachSnap(snap: SnapHelper) {
    snap.attachToRecyclerView(this)
}
fun RecyclerView.detachSnap(snap: SnapHelper) {
    snap.attachToRecyclerView(null)
}

アイテムの中央を表示の中央へ(LinearSnap_CenterToCenter)

アイテムの中央をRecyclerViewの中央へスナップします。

LinearSnap抽象クラスを継承して作ります。

class LinearSnap_CenterToCenter : LinearSnap() {

    // 中央(スナップ先)までの距離と向きを返す..
    override fun distanceToCenter(
        targetView: View,
        helper: OrientationHelper
    ): Int {
        val childCenter = helper.getDecoratedStart(targetView) +
                          helper.getDecoratedMeasurement(targetView) / 2
        val containerCenter = helper.startAfterPadding +
                              helper.totalSpace / 2
        return childCenter - containerCenter
    }

    // 中央(スナップ先)にあるアイテム(View)を返す.
    override fun findCenterView(
        manager: RecyclerView.LayoutManager,
        helper: OrientationHelper
    ): View? {
        val _parentCenter = helper.startAfterPadding +
                            helper.totalSpace / 2

        var _closestChild: View? = null
        var _minDistance = Int.MAX_VALUE
        for(i in 0 until manager.childCount) {
            val _view = manager.getChildAt(i)
            val _childCenter = helper.getDecoratedStart(_view) +
                               helper.getDecoratedMeasurement(_view) / 2
            val _distance = abs(_parentCenter - _childCenter)
            if(_minDistance > _distance) {
                _minDistance = _distance
                _closestChild = _view
            }
        }

        return _closestChild
    }
}
    ...
    lateinit var rcySample: RecyclerView
    lateinit var snap: SnapHelper

    ...
    snap = LinearSnap_CenterToCenter()
    rcySample.attachSnap(snap)
	...

アイテムの上辺を表示の中央へ(LinearSnap_StartToCenter)

アイテムの上辺をRecyclerViewの中央へスナップします。

LinearSnap抽象クラスを継承して作ります。

class LinearSnap_StartToCenter : LinearSnap() {

    // 中央(スナップ先)までの距離と向きを返す..
    override fun distanceToCenter(
        targetView: View,
        helper: OrientationHelper
    ): Int {
        val childStart = helper.getDecoratedStart(targetView)
        val containerCenter = helper.startAfterPadding +
                              helper.totalSpace / 2
        return childStart - containerCenter
    }

    // 中央(スナップ先)にあるアイテム(View)を返す.
    override fun findCenterView(
        manager: RecyclerView.LayoutManager,
        helper: OrientationHelper
    ): View? {
        val _parentCenter = helper.getStartAfterPadding() +
                            helper.getTotalSpace() / 2

        var _closestChild: View? = null
        var _minDistance = Int.MAX_VALUE
        for(i in 0 until manager.childCount) {
            val _view = manager.getChildAt(i)
            val _childStart = helper.getDecoratedStart(_view)
            val _distance = abs(_parentCenter - _childStart)
            if(_minDistance > _distance) {
                _minDistance = _distance
                _closestChild = _view
            }
        }

        return _closestChild
    }
}
    ...
    lateinit var rcySample: RecyclerView
    lateinit var snap: SnapHelper

    ...
    snap = LinearSnap_StartToCenter()
    rcySample.attachSnap(snap)
	...
スポンサーリンク

付録

OrientationHelper(サイズを取得するメソッド集)

SnapHelperやLinearSnapHelperのコード中にOrientationHelperクラスが頻繁に登場します。

OrientationHelperは各部のサイズを取得するメソッド集です。

下図のような値を取得するメソッドが用意されています。参考にして下さい。

OrientationHelper

スポンサーリンク

関連記事:

RecyclerViewは子Viewを並べて表示するコンテナタイプ(ConstraintLayoutと同じ)のViewです。 複数のデータをスクリーン上に一覧表示したい時、例えば電話帳のような「氏名+住所+電話番号」の一覧を表示する場合などに最適です。 アプリを開発していると一覧表示したいデータが多いことに気付きます。 なのでRecyclerViewはとても重要で重宝するViewです。 しかし、思い通りの表示を行わせるためのテクニックが多すぎて、使いこなしが難しいです。 今まで調べたテクニックを忘れないように、整理して書き残そうと思います。 今回は基本の「RecyclerViewの実装」です。 ...
RecyclerViewでアイテムのクリックイベントを取得し、処理を実行する方法を紹介します。 ...
RecyclerViewはアイテムのレイアウトをアイテム毎に変更できます。その時に使う値がViewTypeです。 ViewTypeでアイテムのレイアウトを変更する方法を紹介します。 ...
RecyclerViewは表示が変更される(アイテムの更新、スクロール)時、アイテムのViewをリサイクル(再生利用)します。 これにより余分なViewの作成が行われなくなり、メモリーの節約とパフォーマンスの向上が望めます。 リサイクルはCachedViewsとRecyclerPoolという2つのキャッシュで行われます。 このキャッシュを使ったリサイクルの動作を調べたので紹介します。 ...
RecyclerViewのリサイクル動作で使われるキャッシュは、サイズを大きくすれば多くのViewHolderが保持できます。その分、多くのメモリを消費します。 ViewHolderを多く保持できたとしても、サイクル動作で効率よく使われなければ、メモリの浪費です。 キャッシュのサイズはRecyclerViewの使われ方よって適切なサイズがあります。 そのため、RecyclerViewはキャッシュのサイズを変更できるようになっています。 ...
RecyclerViewはアイテムを一覧表示してくれます。 ただ一覧表示するだけではなく、「追加・削除・移動・切り替え」といったアイテムの表示を効率よく変更する仕組み持っています。 今回はこの仕組みを使ったアイテムの変更方法を紹介します。 ...
RecyclerViewのリサイクル動作で使われるキャッシュは、サイズを大きくすれば多くのViewHolderが保持できます。その分、多くのメモリを消費します。 ViewHolderを多く保持できたとしても、サイクル動作で効率よく使われなければ、メモリの浪費です。 キャッシュのサイズはRecyclerViewの使われ方よって適切なサイズがあります。 そのため、RecyclerViewはキャッシュのサイズを変更できるようになっています。 ...
RecyclerViewが空(アイテムが無い)の時、EmptyViewを表示する実装を行ったので紹介します。 ...
RecyclerView上のアイテムを選択する方法を紹介します。 外部ライブラリー(AndroidX)で提供されるrecyclerview-selection APIを用いた方法です。 ...
RecyclerViewはListView(RecyclerViewの前身)の時に存在していたChoiceModeがありません。 同様な機能が欲しければプログラマ側で実装しなければなりません。 RecyclerView.AdapterをカスタマイズしてChoiceModeを実装してみたので紹介します。 ...
RecyclerViewはアイテムへ装飾を付けることが出来るようになっています。 装飾とは、例えばアイテムの区切り線などです。 今回はアイテムへ装飾を付ける方法を紹介します。 ...
RecyclerViewに表示しきれなかったアイテムはスクロールを行うことで表示されるようになっています。 スクロールは「外部入力(指でスクリーン上をタッチしてスライド)によるスクロール」の他に、「プログラムによるスクロール」をすることも出来ます。 今回はこのアイテムのスクロールについてまとめてみました。 ...
RecyclerViewでアイテムの変更(Change/Insert/Move/Remove)を行うと、変更される様子がアニメーション化されています。 これはデフォルトでアイテムの変更アニメーションが組み込まれているためです。 デフォルトは単純なアニメーションですが、「ある」と「ない」の違いは歴然で、アニメーションのある方が高価なアプリケーションに見えます。 GUI(Graphical User Interface)が主体の携帯端末にとって、利用者に対するアプリの見せ方は重要です。高価に見えた方が使ってもらえる可能性が高くなります。 上記のことから、アプリの機能に関係なくても、ちょっとした動きをアニメーション化するメリットがあります。 2回にわたりアイテムの変更アニメーションについてまとめてみました。 アイテムの変更アニメーション(DefaultItemAnimator、デフォルト) アイテムの変更アニメーション(SimpleItemAnimatorの継承、カスタム) 今回は第1回目「ItemAnimator、デフォルト」編です。デフォルト変更アニメーションの動作について説明します。 ...
RecyclerViewでアイテムの変更(Change/Insert/Move/Remove)を行うと、変更される様子がアニメーション化されています。 これはデフォルトでアイテムの変更アニメーションが組み込まれているためです。 デフォルトは単純なアニメーションですが、「ある」と「ない」の違いは歴然で、アニメーションのある方が高価なアプリケーションに見えます。 GUI(Graphical User Interface)が主体の携帯端末にとって、利用者に対するアプリの見せ方は重要です。高価に見えた方が使ってもらえる可能性が高くなります。 上記のことから、アプリの機能に関係なくても、ちょっとした動きをアニメーション化するメリットがあります。 2回にわたりアイテムの変更アニメーションについてまとめてみました。 アイテムの変更アニメーション(DefaultItemAnimator、デフォルト) アイテムの変更アニメーション(SimpleItemAnimatorの継承、カスタム) 今回は第2回目「SimpleItemAnimatorの継承、カスタム」編です。カスタム変更アニメーションの作り方を説明 ...
RecyclerViewへアイテムが表示されるとき、アニメーションはありません。。一瞬で表示されて終わりです。 「RecyclerViewへアイテムが表示される」ことを、ここでは「アイテムの出現」と言い表すことにします。 このアイテムの出現にアニメーションを付ける方法を紹介します。 アイテムの出現をアニメーションで演出することで、RecyclerViewに表示したい内容が際立つと思います。 ...
スポンサーリンク