ミルクティーと雑記

ミルクティーとプログラミングと趣味と雑記

ViewPager2とBottomNavigationViewのPage位置を同期する

ViewPager2とBottomNavigationViewを組み合わせて使う際、必要なのが位置の同期。

具体的には下記の2つの流れで、位置を同期する必要があります。

①BottomNavigationView→ViewPager2

  • Positionを取得する。

  • ViewPagerの位置を設定する。

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
    int id = menuItem.getItemId();
    switch (menuItemId) {
        case R.id.bnav_home:
            position = 0;
            break;
        ...
    }
    viewPager.setCurrentItem(position, true);
}

動作ログ

D/Logger: BottomNavigationView onNavigationItemSelected position=2
D/Logger: ViewPager            onPageScrollStateChanged state=Settling
D/Logger: ViewPager            onPageSelected position=2
D/Logger: ViewPager            onPageScrollStateChanged state=Idle

②ViewPager2→BottomNavigationView

  • positionを取得する。

  • BottomNavigationViewの位置を設定する。

viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback()
    @Override
    public void onPageSelected(int position) {
        bottomNavigationView.getMenu().getItem(position).setChecked(true);
    }
    ...
}

動作ログ

D/Logger: ViewPager onPageScrollStateChanged state=Dragging
D/Logger: ViewPager onPageScrollStateChanged state=Settling
D/Logger: ViewPager onPageSelected position=2
D/Logger: ViewPager onPageScrollStateChanged state=Idle

onPageSelectedの契機が①と②どちらかを区別する

それぞれの動作ログを見ると分かるのですが、どちらの場合でもonPageSelectedが呼ばれます。

どちらでも呼ばれるからと言って、動作がおかしくなるとかでは全くないのですが、区別したいケースがあったりすると思います。

ここでは呼ばれる契機がどちらかを区別するため、ViewPager2.OnPageChangeCallbackをextendsして改造してみます。

ちなみにViewPager2の実装は下記にあるのですが、その中でOnPageChangeCallbackがabstract classで定義されてます。これをいい感じにします。

android.googlesource.com

上の2つの動作ログのViewPagerに関するもので違いが出てくるのが、state=Draggingの行です。

よって、state=Draggingのタイミングがあったかどうかを保持していけばよさそうです。

実装した結果は下記の通りです。

import androidx.annotation.Px;
import androidx.viewpager2.widget.ViewPager2;

public abstract class OnPageChangeCallback extends ViewPager2.OnPageChangeCallback {

    private boolean isDragging = false;

    public abstract void onPageSelected(int position, boolean isDragged);

    @Override
    public void onPageSelected(int position) {
        onPageSelected(position, isDragging);
    }

    @Override
    public void onPageScrollStateChanged(@ViewPager2.ScrollState int state) {
        switch (state) {
            case ViewPager2.SCROLL_STATE_DRAGGING:
                isDragging = true;
                break;
            case ViewPager2.SCROLL_STATE_IDLE:
                isDragging = false;
                break;
            case ViewPager2.SCROLL_STATE_SETTLING:
            default:
                // NOP
                break;
        }
    }
}

使う側ではこんな感じです。

viewPager.registerOnPageChangeCallback(new OnPageChangeCallback() {
    @Override
    public void onPageSelected(int position, boolean isDragged) {
        if (isDragged) {
            // ViewPagerのスクロール契機だったら、BottomNavigationViewに伝える
            bottomNavigationView.getMenu().getItem(position).setChecked(true);
        }
    }
    ...
});

こうすれば、どっち契機のonPageSelectedかが判別でき、どっちかだけアニメーションしたい、とかいうのも実現できそうです。