user1301593
user1301593

Reputation: 631

Infinite ViewPager with TabLayout

I've implemented an infinite ViewPager in situ https://github.com/JoachimR/AnyDayViewPager. However, instead of using a PagerTabStrip, I'd prefer to use a CoordinatorLayout with TabLayout. But when setting up the TabLayout with the viewpager, (I'm assuming because there are an "infinite number of ViewPager Fragments") the app gets stuck.

How can I solve this issue?

Here's the code (the other files which I have not changed can be found at JoachimR/AnyDayViewPager, e.g., FragmentContent.java, CachingFragmentStatePagerAdapter.java, TimeUtils.java, etc)

MainActivity.java

import android.content.Context;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.view.ViewPager;
import android.support.design.widget.TabLayout;
import android.os.Bundle;

import java.util.Calendar;

public class MainActivity extends FragmentActivity {

    private static Context mContext;

    private CachingFragmentStatePagerAdapter adapterViewPager;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mContext = this;

        ViewPager vpPager = (ViewPager) findViewById(R.id.vpPager);
        adapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
        vpPager.setAdapter(adapterViewPager);

        // set pager to current date
        vpPager.setCurrentItem(TimeUtils.getPositionForDay(Calendar.getInstance()));

        /** THIS IS WHERE THE ISSUES ARISE **/
        TabLayout tabLayoutDiary = (TabLayout) findViewById(R.id.diary_tabs);
        tabLayoutDiary.setupWithViewPager(vpPager);

    }

    public static class MyPagerAdapter extends CachingFragmentStatePagerAdapter {

        private Calendar cal;

        public MyPagerAdapter(FragmentManager fragmentManager) {
            super(fragmentManager);
        }

        @Override
        public int getCount() {
            return TimeUtils.DAYS_OF_TIME;
        }

        @Override
        public Fragment getItem(int position) {
            long timeForPosition = TimeUtils.getDayForPosition(position).getTimeInMillis();
            return FragmentContent.newInstance(timeForPosition);
        }

        @Override
        public CharSequence getPageTitle(int position) {
            Calendar cal = TimeUtils.getDayForPosition(position);
            return TimeUtils.getFormattedDate(mContext, cal.getTimeInMillis());
        }


    }

}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:fab="http://schemas.android.com/tools">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
        <android.support.design.widget.TabLayout
            android:id="@+id/diary_tabs"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:tabMode="scrollable"/>
    </android.support.design.widget.AppBarLayout>
    <android.support.v4.view.ViewPager
        android:id="@+id/vpPager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

</android.support.design.widget.CoordinatorLayout>

Upvotes: 2

Views: 5274

Answers (2)

Ilya
Ilya

Reputation: 21

For example, if you have a day of week as a tab

enum class WeekDay(val abbreviation: String, val position: Int) {
    FALSE_SATURDAY("", 0),
    MONDAY("Mon", 1),
    TUESDAY("Tue", 2),
    WEDNESDAY("Wed", 3),
    THURSDAY("Thu", 4),
    FRIDAY("Fri", 5),
    SATURDAY("Sat", 6),
    FALSE_MONDAY("", 7)
}

When you set up ViewPager adapter bind tabLayout with ViewPager. The current list of pages in the ViewPager adapter should contain two more pages at the zero and the latest position.

If the list of pages always are different:

  • At the zero position contains last page of the previous list of pages in the ViewPager adapter.
  • At the latest position contains first page of the next list of pages in the ViewPager adapter

If list of pages always are same:

  • At the zero position contains last page in list.
  • At the latest position contains first page in list.

Image explainer

TabLayoutMediator(
        binding.tabLayout, binding.viewPager2
    ) { tab: TabLayout.Tab, position: Int ->
  
        if (position == WeekDay.FALSE_MONDAY.position || position == WeekDay.FALSE_SATURDAY.position) {
            tab.view.visibility = View.GONE
        }
        
        tab.text = listOf(
            WeekDay.FALSE_SATURDAY.abbreviation,
            WeekDay.MONDAY.abbreviation,
            WeekDay.TUESDAY.abbreviation,
            WeekDay.WEDNESDAY.abbreviation,
            WeekDay.THURSDAY.abbreviation,
            WeekDay.FRIDAY.abbreviation,
            WeekDay.SATURDAY.abbreviation,
            WeekDay.FALSE_MONDAY.abbreviation
        )[position]
    }.attach()

Then you need to registrate callback at ViewPager2

binding.viewPager2.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
        private var state = -1
        override fun onPageScrollStateChanged(state: Int) {
            super.onPageScrollStateChanged(state)

            if (binding.viewPager2.currentItem == WeekDay.FALSE_SATURDAY.position && this.state == SCROLL_STATE_SETTLING) {
                binding.viewPager2.setCurrentItem(WeekDay.SATURDAY.position, false)
                
            }
            if (binding.viewPager2.currentItem == WeekDay.FALSE_MONDAY.position && this.state == SCROLL_STATE_SETTLING) {
                binding.viewPager2.setCurrentItem(WeekDay.MONDAY.position, false)
                
            }
            this.state = state
    }})

this.state == SCROLL_STATE_SETTLING

means that user lifted their finger after dragging

Upvotes: 2

danh32
danh32

Reputation: 6244

Possibly due to this bug: https://code.google.com/p/android/issues/detail?id=180027

TabLayout creates a TextView tab for each page in your ViewPager, rather than dynamically creating and recycling the TextViews. As you're seeing, this will just blow up if you try to fake out an infinite ViewPager.

Unfortunately, there's no fix for now. You can try using https://github.com/nshmura/RecyclerTabLayout which was linked on the bug, but I've not used it myself.

Upvotes: 4

Related Questions