ViewPager + TabLayoutで選択済みタブを再選択したときに処理を行う


追記(2017/2/14)

ちょっと今さらな感じはありますが、Support Library 24.0.0からTabLayoutに複数のリスナーが設定できるようになりましたので、そちらのやり方も追記しました。

TabLayoutで選択済みタブを再選択したときに処理を行いたい場合、TabLayout.OnTabSelectedListenerというインターフェースのonTabReselectedを実装して、TabLayoutに設定する必要があります。

Support Library 24.0.0より前のバージョンでのやりかた

ViewPagerとTabLayoutの連動について

通常、ViewPagerと連動させる場合はTabLayout.setupWithViewPagerを実行することになりますが、このメソッドの中でTabLayout.OnTabSelectedListenerもよしなに設定してくれるようです。
このとき設定されるリスナーは、TabLayoutの内部クラスとして定義されているTabLayout.ViewPagerOnTabSelectedListenerになります。

このリスナーは、onTabReselectedメソッドの実装が空なので、選択済みタブを再選択しても何も起きません。
そのため、何かしら処理を行いたい場合は独自に実装したリスナーをTabLayoutに設定する必要があります。

対応方法

独自に実装したリスナーを設定する場合、setupWithViewPagerでよしなに設定されている箇所を個別に対応していけばよいのですが、どうもSupportLibrary 23.4.0時点のTabLayoutに実装されているpublicメソッドでは、setupWithViewPagerで行っている処理をすべて代替することができないようです。

ソースを見たところ、今回問題になっているリスナーの設定はメソッドが用意されているので可能なのですが、他の処理で代替できない箇所があるため、かゆいところに手が届かない感じでした。
duplicatedになっているメソッドがsetupWithViewPagerで使われていたりもするので、基本的に連動させるときはsetupWithViewPagerを使うことが前提になっているようです。

そのため、実現するためには以下のどちらかで対応したほうがよさそうです。

1. TabLayoutには何も手を加えず、setupWithViewPagerを実行したあとに独自に実装したTabLayout.OnTabSelectedListenerを設定する。

メリット

  • 用意されているAPIで対応できるため、事前の準備が必要ない。

デメリット

  • setupWithViewPagerを実行した後に、リスナーを再度設定する必要がある。 ※リスナーは、最後に設定したものが利用されるので順序が逆ではダメ
  • setupWithViewPager内でもリスナーが生成されているので、リスナーのインスタンス生成回数が1回余分に増える。

2. TabLayoutを継承して、setupWithViewPagerと同等の処理を個別に行うメソッドを実装する。

メリット

  • 1と比べて余分なインスタンス生成が発生せず、柔軟にリスナーを切り替えることができるようになる。

デメリット

  • 独自TabLayoutを使うときに意識する作法が増える。 ※実装方法によりますが、標準のsetupWithViewPagerを使わない等が挙げられると思います。

手軽なのは1ですね。リスナーのインスタンス生成が1回余分に発生するところがよくないですが、この処理を行うActivityが何度も生成・破棄されないという前提であれば、この方法でもありかなと思います。
この記事では1の方法で対応します。

実際の対応について

やることは書いてある通り、setupWithViewPagerを実行したあとに独自に実装したTabLayout.OnTabSelectedListenerを設定するだけなのですが、単純にこのインターフェースを実装したものをTabLayoutに設定するとViewPagerとの連動がうまく行われなくなってしまいます。

そこで、setupWithViewPagerの中で設定しているTabLayout.ViewPagerOnTabSelectedListenerを継承してリスナーを定義します。onTabReselectedメソッドは、元々は空の実装であるためオーバーライドしてもViewPagerとの連動に影響がありません。
あとは、このリスナーを設定してあげれば大丈夫です。

サンプルでは、ViewPagerOnTabSelectedListenerを継承した無名クラスを設定しています。

        tabLayout.setupWithViewPager(viewPager);

        TabLayout.OnTabSelectedListener onTabSelectedListener = new TabLayout.ViewPagerOnTabSelectedListener(viewPager) {
            @Override
            public void onTabReselected(TabLayout.Tab tab) {
                // 選択済みタブが再選択されたときに行いたい処理を書く
            }
        };

        tabLayout.setOnTabSelectedListener(onTabSelectedListener);

TabLayout.OnTabSelectedListenerに定義されているonTabUnselectedについても同じ要領で対応することができます。(あまり利用する場面が思い浮かびませんが。。。

サンプルをGitHubに上げています。
https://github.com/djyugg/Android-TabLayout-ReselectSample/tree/support-library-23.4.0

Support Library 24.0.0以降のやりかた

Support Library 24.0.0以降では、TabLayout.setOnTabSelectedListenerがdeprecatedとなり、TabLayout.addOnTabSelectedListenerが追加されました。
このメソッドを使うことによって、既に設定されてあるリスナーを上書きせずにリスナーを追加することができるようです。
よって、TabLayout.OnTabSelectedListenerを実装してTabLayout.addOnTabSelectedListenerに追加してあげれば、ViewPagerとの連動もそのままで処理を追加できます。

tabLayout.setupWithViewPager(mViewPager);

tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
    @Override
    public void onTabSelected(TabLayout.Tab tab) {
    }

    @Override
    public void onTabUnselected(TabLayout.Tab tab) {
        Log.v("OnTabSelectedListener", "onTabUnselected SECTION " + tab.getTag());
    }

    @Override
    public void onTabReselected(TabLayout.Tab tab) {
        Log.v("OnTabSelectedListener", "onTabReselected SECTION " + tab.getTag());
    }
});

GitHubのサンプルも、こちらの内容で更新しました。
https://github.com/djyugg/Android-TabLayout-ReselectSample