Android: ViewPager gets stuck in between views
Asked Answered
R

9

22

I have a ViewPager which swipes between Fragments. I'm using a FragmentStatePagerAdapter to feed the Fragments to the ViewPager. If the user swipes left at a normal pace, and then swipes right very quickly, they can get the ViewPager into a weird state where it shows multiple Fragments.

For example, if the user is on Fragment A, then swipes left to Fragment B at a normal pace, and then quickly swipes right to go back to Fragment A, then on screen shows both Fragments A & B.

Anybody have any ideas on why this is happening or a good way to prevent it?

Here's what it looks like: enter image description here

Here's my ViewPager definition in XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

<com.company.views.CustomActionBar
    android:id="@+id/customActionBar"
    android:layout_width="match_parent"
    android:layout_height="@dimen/height_actionbar"
    android:layout_alignParentTop="true"/>

<android.support.v4.view.ViewPager
    android:id="@+id/viewPager"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_below="@id/customActionBar"/>

Also, I logged output from my onPageChangeListener() and noticed that when the ViewPager gets stuck in between views, it's reporting a positionOffset of 0. Here's what the ViewPager's values look like as it transitions from STATE_DRAGGING to STATE_SETTLING to STATE_IDLE when it lands in this weird state:

state = 0 prevState: 2 position: 1 positionOffset: 0.0

state = 1 prevState: 0 position: 1 positionOffset: 0.0

state = 2 prevState: 1 position: 1 positionOffset: 0.4069444

state = 0 prevState: 2 position: 2 positionOffset: 0.0

So it appears as if the ViewPager is reporting the wrong positionOffset back to me.

Full sample code Activity and Adapter:

public class ActivityBagelProfileViewer extends CustomAbstractFragmentActivity
    implements CustomActionBarContract, ListenerProgress, ListenerSync
{
public static final String EXTRA_BAGEL_INDEX = "BAGEL";

public static final int REQUEST_CODE_BAGEL_PROFILE_VIEWER = 4000;
public static final int RESULT_GO_TO_PASS_FLOW = 12;
public static final int RESULT_GO_TO_LIKE_FLOW = 14;
public static final int RESULT_GO_TO_SEE_MORE_BAGELS = 16;

private ViewPager mProfilesViewPager;
private CustomActionBar mCustomActionBar;
private int mViewPagerPosition;

private DialogProgress mDialogProgress;

private BagelViewPagerAdapter mAdapterBagelViewPager;
private List<Bagel> mListBagels;

@Override
protected void onCreate(Bundle savedInstanceState)
{
    Logger.d("ENTER");

    super.onCreate(savedInstanceState);

    if (ManagerGive.IS_BRANCH_SESSION_OPEN == false)
    {
        ManagerGive.initializeBranchMetricsSession();
    }

    setContentView(R.layout.activity_with_viewpager);

    mCustomActionBar = (CustomActionBar) findViewById(R.id.customActionBar);
    mCustomActionBar.setMenu(this);

    mProfilesViewPager = (ViewPager) findViewById(R.id.viewPager);

    if (getIntent().getExtras() != null)
    {
        mViewPagerPosition = getIntent().getExtras().getInt(EXTRA_BAGEL_INDEX, 0);
    }
}

@Override
protected void onStop()
{
    super.onStop();
    ManagerGive.closeBranchMetricsSession();
}

public void onIconClick(View view)
{
    Logger.d("ENTER");
    finishWithAnimation();
}

private void finishWithAnimation()
{
    setResult(RESULT_OK);
    finish();
    overridePendingTransition(R.anim.slide_in_from_left, R.anim.slide_out_to_right);
}

@Override
public void onBackPressed()
{
    if (!super.handleBackPressedEvent())
    {
        finishWithAnimation();
    }
}


private void setupNewAdapter()
{
    mListBagels = Bakery.getInstance().getManagerBagel().getCopyOfBagelsWithoutCurrent();
    mAdapterBagelViewPager = new BagelViewPagerAdapter(getSupportFragmentManager(), mListBagels, this);
    mProfilesViewPager.setAdapter(mAdapterBagelViewPager);

    mProfilesViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener()
    {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
        {
        }

        @Override
        public void onPageSelected(int position)
        {
            setActionBar(position);
            mViewPagerPosition = position;
        }

        @Override
        public void onPageScrollStateChanged(int state)
        {
        }
    });

    mProfilesViewPager.setCurrentItem(mViewPagerPosition, false);
}

@Override
protected void onResume()
{
    Logger.d("ENTER");
    super.onResume();

    Bakery.getInstance().getManagerSyncData().addListener(this);

    if (mProfilesViewPager.getAdapter() == null)
    {
        Logger.d("Adapter null. Setting new adapter");
        setupNewAdapter();
    }
    else
    {
        if (mProfilesViewPager.getAdapter().getCount() !=
                Bakery.getInstance().getManagerBagel().getCopyOfBagelsWithoutCurrent().size())
        {
            Logger.d("Bagel list in Bakery changed size. Setting new adapter");
            setupNewAdapter();
        }
    }

    if (mListBagels.size() > 0)
    {
        setActionBar(mViewPagerPosition);
        mDialogProgress = new DialogProgress(this);
    }
    else
    {
        //kv Something has gone terribly wrong if we don't have any Bagels, just finish
        finish();
    }
}

private void setActionBar(int bagelIndex)
{
    Logger.d("bagelIndex=" + bagelIndex);

    Bagel bagel = mListBagels.get(bagelIndex);

    //kv If this is our current bagel and we haven't taken action yet, then show timer
    if (Bakery.getInstance().getManagerBagel().getCurrentBagel() == bagel
            && bagel.getAction() != Bagel.ACTION_LIKED && bagel.getAction() != Bagel.ACTION_PASSED)
    {
        Logger.d("Setting up #timer in action bar");
        mCustomActionBar.startTimeLeftTimer(DateUtils.getMillisFromUtc(bagel.getEndDate()),
                this, new ListenerTimer()
                {
                    @Override
                    public void onTimerExpired()
                    {
                        Logger.d("ENTER");
                        Bakery.getInstance().getManagerSyncData().performSync(null, false);
                    }
                }, mCustomActionBar.getTextViewTimeLeft(), R.string.timer_blank);
        mCustomActionBar.setLabel(R.string.time_left);
        mCustomActionBar.hideTitle();
    }
    //kv Otherwise show date
    else
    {
        mCustomActionBar.setTitle(DateUtils.getLocalizedDateFromStringDate(bagel.getStartDate(), DateUtils.DATE_WITH_TIME_PATTERN));
        mCustomActionBar.stopTimeLeftTimer();
        mCustomActionBar.hideTimeLeft();
    }
}

@Override
protected void onSaveInstanceState(Bundle outState)
{
    super.onSaveInstanceState(outState);
    outState.putInt(EXTRA_BAGEL_INDEX, mViewPagerPosition);
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState)
{
    Logger.d("ENTER");

    super.onRestoreInstanceState(savedInstanceState);
    if (savedInstanceState.containsKey(EXTRA_BAGEL_INDEX))
    {
        mViewPagerPosition = savedInstanceState.getInt(EXTRA_BAGEL_INDEX);
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
    super.onActivityResult(requestCode, resultCode, data);
    Logger.d("requestCode=" + requestCode + ", resultCode=" + resultCode + ", data=" + data);

    switch (requestCode)
    {
        case ActivityBeanShop.REQUEST_CODE:
            if (resultCode == Activity.RESULT_OK && data != null)
            {
                //fp user purchased sufficient beans to resume their transaction
                PurchaseType interruptedPurchaseType = (PurchaseType) data.getSerializableExtra(ActivityBeanShop.EXTRA_PURCHASE_TYPE);

                switch (interruptedPurchaseType)
                {
                    case BONUS_BAGEL:
                    case OPEN_SESAME:
                    case REMATCH:
                        Bundle bundle = new Bundle();
                        bundle.putSerializable(ManagerPurchase.EXTRA_PURCHASE_TYPE, interruptedPurchaseType);
                        ManagerEvents.notifyListeners(EventType.BEAN_TRANSACTION_FOR_FEATURE_UNLOCK_COMPLETE, bundle);
                        Logger.d("Notified listeners about #purchase bean transaction, can now resume feature #purchase");
                        break;

                    default:
                        Logger.w("Unrecognized purchase type: " + interruptedPurchaseType.getItemName());
                }
            }

            break;
        default:
            Logger.w("Could not recognize code: " + requestCode);
    }
}

@Override
public int getTitleId()
{
    return R.string.bagel_action_checked;
}

@Override
public int getIconId()
{
    return R.drawable.selector_icon_up;
}

@Override
public void showProgress(int stringId)
{
    mDialogProgress.setText(stringId);
    mDialogProgress.show();
}

@Override
public void dismissProgress()
{
    ViewUtils.safelyDismissDialog(mDialogProgress);
}

public void setActionBar()
{
    setActionBar(mViewPagerPosition);
}

@Override
public void onSyncComplete()
{
    Logger.d("ENTER");
    mListBagels = Bakery.getInstance().getManagerBagel().getCopyOfBagelsWithoutCurrent();
    mAdapterBagelViewPager.setBagels(mListBagels);
}

public boolean isShowingThisBagel(Bagel bagel)
{
    Bagel currentlyShownBagel = mListBagels.get(mViewPagerPosition);
    return bagel == currentlyShownBagel;
}

private static class BagelViewPagerAdapter extends FragmentStatePagerAdapter
{
    private List<Bagel> mBagels;
    private ListenerProgress mListenerProgress;

    public BagelViewPagerAdapter(FragmentManager fragmentManager, List<Bagel> bagels,
                                 ListenerProgress listenerProgress)
    {
        super(fragmentManager);
        Logger.d("bagels=" + bagels);
        this.mBagels = bagels;
        mListenerProgress = listenerProgress;
    }

    @Override
    public Fragment getItem(int i)
    {
        Logger.d("i=" + i);
        UserProfile myProfile = Bakery.getInstance().getManagerUserProfile().getMyOwnProfile();
        FragmentProfile fragment = FragmentProfile.newInstance(mBagels.get(i), false, myProfile);
        fragment.setListenerProgress(mListenerProgress);
        return fragment;
    }

    @Override
    public int getCount()
    {
        return mBagels.size();
    }

    public void setBagels(List<Bagel> bagels)
    {
        mBagels = bagels;
        notifyDataSetChanged();
    }
}
}

And here's the XML layout code for the layout of each Fragment (had to cut some out b/c of SO char limit):

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/scrollView">

<LinearLayout
    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="wrap_content"
    android:layout_marginTop="-0.5dp"
    android:orientation="vertical"
    android:animateLayoutChanges="true"
    android:id="@+id/profile_top_container">

    <!-- Photos section with pager/carousel -->
    <FrameLayout
        android:id="@+id/photoViewpagerContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <com.coffeemeetsbagel.views.CustomAsShitViewPager
            android:id="@+id/pager_profile_images"
            xmlns:android="http://schemas.android.com/apk/res/android"
            app:aspectRatio="@integer/photo_ratio_height_over_width"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <LinearLayout
            android:id="@+id/linearLayout_bulletsAndFriendsContainer"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical"
            android:gravity="bottom">

            <com.coffeemeetsbagel.views.CustomTextView
                android:id="@+id/textView_stamp"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:visibility="invisible"
                app:customFont="Raleway-Bold.ttf"
                android:layout_gravity="end"
                android:textSize="@dimen/text_stamp"
                android:paddingTop="@dimen/margin_large"
                android:layout_marginEnd="@dimen/margin_xxxxxsmall"
                android:layout_marginRight="@dimen/profile_margin_smaller"/>

            <!-- photo circle indicators -->
            <com.viewpagerindicator.CirclePageIndicator
                android:id="@+id/bullet_indicators"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/circle_indicator_margin_bottom"
                android:clickable="false"
                app:fillColor="@color/blue_cmb"
                app:pageColor="@color/gray_background"
                app:radius="@dimen/circle_indicator_radius"
                app:strokeWidth="0dp"/>

            <!-- container for mutual friends strip -->
            <RelativeLayout
                android:id="@+id/relativeLayout_mutual_friends_container"
                android:layout_width="match_parent"
                android:layout_height="@dimen/baseline_grid_component_touchable"
                android:background="@color/white_transparent"
                android:visibility="gone">

                <com.coffeemeetsbagel.views.CustomTextView
                    android:id="@+id/textView_mutual_friends_label"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_alignParentStart="true"
                    android:layout_alignParentLeft="true"
                    style="@style/profile_mutual_friends_text"/>

                <LinearLayout
                    android:id="@+id/linearLayout_mutual_friends_icons"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:orientation="horizontal"
                    android:layout_alignParentEnd="true"
                    android:layout_alignParentRight="true"
                    android:layout_marginEnd="@dimen/baseline_grid_small"
                    android:layout_marginRight="@dimen/baseline_grid_small"
                    android:layout_centerVertical="true">

                    <ImageView
                        android:id="@+id/imageView_icon0"
                        android:layout_width="@dimen/baseline_grid_component_touchable"
                        android:layout_height="@dimen/baseline_grid_component_touchable"
                        android:padding="@dimen/typography_smallest"
                        android:background="@color/transparent"
                        android:visibility="gone"/>

                    <ImageView
                        android:id="@+id/imageView_icon1"
                        android:layout_width="@dimen/baseline_grid_component_touchable"
                        android:layout_height="@dimen/baseline_grid_component_touchable"
                        android:background="@color/transparent"
                        android:padding="@dimen/typography_smallest"
                        android:visibility="gone"/>

                    <ImageView
                        android:id="@+id/imageView_icon2"
                        android:layout_width="@dimen/baseline_grid_component_touchable"
                        android:layout_height="@dimen/baseline_grid_component_touchable"
                        android:background="@color/transparent"
                        android:padding="@dimen/typography_smallest"
                        android:visibility="gone"/>
                </LinearLayout>
            </RelativeLayout>
        </LinearLayout>
    </FrameLayout>

    <!-- Buttons section with User Actions for pass / like-->
    <LinearLayout
        android:id="@+id/linearLayout_buttons_pass_like"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="@dimen/baseline_grid_smaller"
        android:layout_marginLeft="@dimen/baseline_grid_small"
        android:layout_marginRight="@dimen/baseline_grid_small"
        android:layout_marginTop="@dimen/baseline_grid_medium"
        android:orientation="horizontal"
        android:visibility="gone">

        <ImageView
            android:id="@+id/button_pass"
            android:layout_width="0dp"
            android:layout_height="@dimen/profile_action_button_height"
            android:layout_weight="1"
            android:background="@drawable/ripple_button_pass"
            android:clickable="true"
            android:src="@drawable/icon_pass_pressed"
            android:scaleType="center"
            android:layout_marginRight="@dimen/margin_small"/>

        <ImageView
            android:id="@+id/button_like"
            android:layout_width="0dp"
            android:layout_height="@dimen/profile_action_button_height"
            android:layout_weight="1"
            android:background="@drawable/ripple_button_like"
            android:clickable="true"
            android:src="@drawable/icon_like_pressed"
            android:scaleType="center"
            android:layout_marginLeft="@dimen/margin_small"/>
    </LinearLayout>

    <!-- Buttons section with User Actions for rematch / give-->
    <LinearLayout
        android:id="@+id/linearLayout_buttons_rematch_give"
        android:layout_width="match_parent"
        android:layout_height="@dimen/give_ten_button_height"
        android:layout_marginBottom="@dimen/baseline_grid_smaller"
        android:layout_marginLeft="@dimen/baseline_grid_small"
        android:layout_marginRight="@dimen/baseline_grid_small"
        android:layout_marginTop="@dimen/baseline_grid_medium"
        android:orientation="horizontal"
        android:gravity="center"
        android:visibility="gone">

        <com.coffeemeetsbagel.views.CustomTextView
            android:id="@+id/textView_rematch"
            android:layout_width="@dimen/zero_dip"
            android:layout_height="match_parent"
            android:layout_marginRight="@dimen/give_take_button_margin_side"
            android:layout_weight="1"
            style="@style/button_give_take_rematch"
            android:text="@string/rematch"/>

        <com.coffeemeetsbagel.views.CustomTextView
            android:id="@+id/text_view_give_with_rematch"
            android:layout_width="@dimen/zero_dip"
            android:layout_weight="1"
            android:layout_height="match_parent"
            style="@style/button_give_take_rematch"
            android:text="@string/give"/>
    </LinearLayout>

    <com.coffeemeetsbagel.views.CustomTextView
        android:id="@+id/textView_they_like_you"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:drawableLeft="@drawable/icon_like_alert"
        android:drawablePadding="@dimen/margin_xxsmall"
        style="@style/profile_info_item_value"
        android:layout_marginLeft="@dimen/margin_med"
        android:paddingTop="@dimen/baseline_grid_smaller"/>

    <ViewStub
        android:id="@+id/viewStub_profile_feedback"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout="@layout/profile_feedback"/>

    <!-- Profile information table -->
    <!-- Name -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  style="@style/profile_info_item_layout_margins"
                  android:paddingTop="@dimen/baseline_grid_smaller"
                  android:orientation="horizontal">

        <com.coffeemeetsbagel.views.CustomTextView
            android:text="@string/profile_info_label_name"
            style="@style/profile_info_item_label"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"/>

        <com.coffeemeetsbagel.views.CustomTextView
            android:id="@+id/profile_info_value_name"
            style="@style/profile_info_item_value"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"/>
    </LinearLayout>

    <!-- Age -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  style="@style/profile_info_item_layout_margins"
                  android:orientation="horizontal">

        <com.coffeemeetsbagel.views.CustomTextView
            android:text="@string/profile_info_label_age"
            style="@style/profile_info_item_label"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            />

        <com.coffeemeetsbagel.views.CustomTextView
            android:id="@+id/profile_info_value_age"
            style="@style/profile_info_item_value"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"/>
    </LinearLayout>

    <!-- Location -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  style="@style/profile_info_item_layout_margins"
                  android:orientation="horizontal">

        <com.coffeemeetsbagel.views.CustomTextView
            android:text="@string/location"
            style="@style/profile_info_item_label"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            />

        <com.coffeemeetsbagel.views.CustomTextView
            android:id="@+id/profile_info_value_location"
            style="@style/profile_info_item_value"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"/>
    </LinearLayout>

    <!-- Ethnicity -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  style="@style/profile_info_item_layout_margins"
                  android:orientation="horizontal">

        <com.coffeemeetsbagel.views.CustomTextView
            android:text="@string/profile_info_label_ethnicity"
            style="@style/profile_info_item_label"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            />

        <com.coffeemeetsbagel.views.CustomTextView
            android:id="@+id/profile_info_value_ethnicity"
            style="@style/profile_info_item_value"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"/>
    </LinearLayout>

    <!-- Height -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  style="@style/profile_info_item_layout_margins"
                  android:orientation="horizontal">

        <com.coffeemeetsbagel.views.CustomTextView
            android:text="@string/profile_info_label_height"
            style="@style/profile_info_item_label"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            />

        <com.coffeemeetsbagel.views.CustomTextView
            android:id="@+id/profile_info_value_height"
            style="@style/profile_info_item_value"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"/>
    </LinearLayout>

    <!-- Religion -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  style="@style/profile_info_item_layout_margins"
                  android:orientation="horizontal">

        <com.coffeemeetsbagel.views.CustomTextView
            android:text="@string/profile_info_label_religion"
            style="@style/profile_info_item_label"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            />

        <com.coffeemeetsbagel.views.CustomTextView
            android:id="@+id/profile_info_value_religion"
            style="@style/profile_info_item_value"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"/>
    </LinearLayout>

    <!-- Occupation -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  style="@style/profile_info_item_layout_margins"
                  android:orientation="horizontal">

        <com.coffeemeetsbagel.views.CustomTextView
            android:text="@string/profile_info_label_occupation"
            style="@style/profile_info_item_label"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            />

        <com.coffeemeetsbagel.views.CustomTextView
            android:id="@+id/profile_info_value_occupation"
            style="@style/profile_info_item_value"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="2"/>
    </LinearLayout>

    <!-- Employer -->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  style="@style/profile_info_item_layout_margins"
                  android:orientation="horizontal">

...

Radicalism answered 21/7, 2015 at 21:30 Comment(9)
I am having same issue as you do, still waiting for a reply, by the way is your activity doing a lot of work on UI thread because that seems a possible reason to get stuck. My thread is here #31537539Sticky
@Gaurav: I work on the same team as Karim here, so I can say that barring inflating a large xml, we're not doing anything very expensive/long running on the UI thread. And even that should not matter so much since these aviews have already been built by the viewpager, and we're only moving them onscreen or offscreen....Rivalee
Yeah, totally agreed, one thing I noticed that if I change viewpager by setCurrentItem() there is no issue, everything's works really normal and animations work fluidly. Only issue if we do manually by swipeSticky
What do you use for Sliding? I used the Google's SlidingTabsBasic project. I don't have your problem.Stingy
Since you're using a ViewPager, you should be using a layout in it. Pls post the layout and the relevant code. There's not much time left.Stingy
Added layout for each Fragment, but couldn't fit it all.Radicalism
I'll review the layout just posted. I suggest you add another tag of "android-layout" since I think the issue is related to layouts. I think the layout is rather complicated and a challenge for anyone to troubleshoot.Stingy
Done, thanks for taking a look!Radicalism
I posted another answer. I wish i have noticed the issue earlier and posted much earlier.Stingy
S
21

I've noticed that I see this issue if I have some animations given by the animateLayoutChanges. Just deactivating it in the xml file, prevents the pages to be stuck in the middle.

Should answered 22/6, 2016 at 7:47 Comment(4)
Worked for me. But now I will have to apply animations for smooth transitions of view visibility.Sackville
Should accept as the right answer. Let us help other users to find quickly right advice!Core
After removing animateLayoutChanges, this problem was solved for me. It's clearly a timing thing - you swipe fast, and the animation isn't done yet, and it makes views show on screen that shouldn't.Diaphoretic
Yep, seems like any kind of animation would do that.Docilu
B
3

Try the following sample code and modify it as per your requirement(I guess your are loading the image on main UI thread and not caching it, its just a guess). In this code i am downloading and caching the images from the internet : Create a Activity class named SomeFragTest

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.util.LruCache;
import android.support.v4.view.ViewPager;
import android.util.Log;
import android.widget.ImageView;
public class SomeFragTest extends FragmentActivity{
private LruCache<String, Bitmap> cache;
private List<String> strings;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_layout);
ViewPager mViewPager = (ViewPager)findViewById(R.id.viewPager);

strings=new ArrayList<String>();
setData();
int memClass = ( ( ActivityManager )getSystemService(           Context.ACTIVITY_SERVICE ) ).getMemoryClass();
int cacheSize = 1024 * 1024 * memClass / 8;

cache=new LruCache<String, Bitmap>(cacheSize){
    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getByteCount()/1024;
    }
};
mViewPager.setOffscreenPageLimit(strings.size());
mViewPager.setAdapter(new MyPageAdapter(getSupportFragmentManager()));
}
private void setData()
{
for (int i = 1; i <= 10; i++) {
    strings.add("http://dummyimage.com/600x400/000/0011ff.png&text="+i);
}
}
public void loadBitmap(int position , ImageView imageView) {
imageView.setImageResource(R.drawable.ic_launcher);
imageView.setTag(strings.get(position));
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
task.execute(strings.get(position));
}
class MyPageAdapter extends FragmentPagerAdapter
{
public MyPageAdapter(FragmentManager fm) {
    super(fm);
    // TODO Auto-generated constructor stub
}

@Override
public Fragment getItem(int arg0) {
    Fragment fragment=new ChildFrag();
    Bundle bundle=new Bundle();
    bundle.putInt("POS", arg0);
    fragment.setArguments(bundle);
    return fragment;
}

@Override
public int getCount() {
    return strings.size();
}


 }
class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
public String url;
private final WeakReference<ImageView> imageViewReference;


public BitmapDownloaderTask(ImageView imageView) {
    imageViewReference = new WeakReference<ImageView>(imageView);

}

@Override
// Actual download method, run in the task thread
protected Bitmap doInBackground(String... params) {

     // params comes from the execute() call: params[0] is the url.
    url=params[0];
    if(cache.get(url)!=null){
        Log.e("FROM ", "CACHE");
        return cache.get(url);
    }
     return downloadBitmap(params[0]);
}
private Bitmap downloadBitmap(String url) {
    Log.e("FROM ", "URL");
   HttpClient client=new DefaultHttpClient();
    //final AndroidHttpClient client =     AndroidHttpClient.newInstance("Android");
    final HttpGet getRequest = new HttpGet(url);

    try {
        HttpResponse response = client.execute(getRequest);
        final int statusCode = response.getStatusLine().getStatusCode();
        if (statusCode != HttpStatus.SC_OK) { 
            Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url); 
            return null;
        }

        final HttpEntity entity = response.getEntity();
        if (entity != null) {
            InputStream inputStream = null;
            try {
                inputStream = entity.getContent(); 
                //final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                return decodeBitmapWithGiveSizeFromResource(inputStream);
            } finally {
                if (inputStream != null) {
                    inputStream.close();  
                }
                entity.consumeContent();
            }
        }
    } catch (Exception e) {
        // Could provide a more explicit error message for IOException or IllegalStateException
        getRequest.abort();
        Log.w("ImageDownloader", "Error while retrieving bitmap from " + url);
        Log.e("ERROR", " " +e.getLocalizedMessage());
    } finally {
        if (client != null) {
            //client.close();
        }
    }
    return null;
}

/***************/
private void copy(InputStream inputStream,ByteArrayOutputStream arrayOutputStream)
{

            byte[] buffer = new byte[1024];
    int len;
    try {
        while ((len = inputStream.read(buffer)) > -1 ) {
           arrayOutputStream.write(buffer, 0, len);
        }
        arrayOutputStream.flush();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

}
private  Bitmap decodeBitmapWithGiveSizeFromResource(InputStream inputStream) {
    //BufferedInputStream bufferedInputStream=new BufferedInputStream(inputStream);
    final BitmapFactory.Options options = new BitmapFactory.Options();
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    copy(inputStream,out);
   InputStream in2 = new ByteArrayInputStream(out.toByteArray());

    options.inJustDecodeBounds = true;

        BitmapFactory.decodeStream(inputStream, null, options);
    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    Bitmap bitmap=BitmapFactory.decodeStream(in2,null, options);
    try {
        inputStream.close();
        in2.close();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

    return scaleDown(bitmap,false);
}
private  Bitmap scaleDown(Bitmap realImage, boolean filter) {

    Bitmap newBitmap = Bitmap.createScaledBitmap(realImage, 100,
            100, filter);

    Bitmap output = Bitmap.createBitmap(newBitmap.getWidth(), newBitmap
            .getHeight(), Config.ARGB_8888);
    Canvas canvas = new Canvas(output);

    final int color = 0xff424242;
    final Paint paint = new Paint();
    final Rect rect = new Rect(0, 0, newBitmap.getWidth(), newBitmap.getHeight());
    final RectF rectF = new RectF(rect);
    final float roundPx = 10;

    paint.setAntiAlias(true);
    canvas.drawARGB(0, 0, 0, 0);
    paint.setColor(color);
    canvas.drawRoundRect(rectF, roundPx, roundPx, paint);

    paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
    canvas.drawBitmap(newBitmap, rect, rect, paint);

    return output;
}
private  int calculateInSampleSize(BitmapFactory.Options options) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;
    if (height > 100 || width > 100) {

        final int halfHeight = height / 2;
        final int halfWidth = width / 2;

        // Calculate the largest inSampleSize value that is a power of 2 and keeps both
        // height and width larger than the requested height and width.
        while ((halfHeight / inSampleSize) >100
                && (halfWidth / inSampleSize) >100) {
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
} 
@Override
// Once the image is downloaded, associates it to the imageView
protected void onPostExecute(Bitmap bitmap) {
    if (isCancelled()) {
        bitmap = null;
    }

    if (imageViewReference != null) {
        cache.put(url, bitmap);
        ImageView imageView = imageViewReference.get();

   //     BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
        // Change bitmap only if this process is still associated with it

        if (((String)imageView.getTag()).equalsIgnoreCase(url)) {
            imageView.setImageBitmap(bitmap);

        }
    }
}
   }
}

After this create the xml for it, named activity_layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<android.support.v4.view.ViewPager
    android:id="@+id/viewPager"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</LinearLayout>

Now we have create the Fragment class that we want to inflate in the ViewPager: Create a class named ChildFrag as follows

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

public class ChildFrag extends Fragment {
private int index;
private ImageView imageView;

@Override
@Nullable
public View onCreateView(LayoutInflater inflater,
        @Nullable ViewGroup container, @Nullable Bundle savedInstanceState)     {
    View view = inflater.inflate(R.layout.fragtest, container, false);
    index = getArguments().getInt("POS");
    ((TextView) view.findViewById(R.id.textView1)).setText("" + index);
    imageView = (ImageView) view.findViewById(R.id.imageView1);
    return view;
}

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    ((SomeFragTest) getActivity()).loadBitmap(index, imageView);
}
}

Now we have create the xml for the fragment as fragtest:

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
    android:id="@+id/textView1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Large Text"
    android:textAppearance="?android:attr/textAppearanceLarge" />

<ImageView
    android:id="@+id/imageView1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/ic_launcher" />
 </LinearLayout>

Add the following permission in the AndroidManifest.xml

 <uses-permission android:name="android.permission.INTERNET" />
Boleslaw answered 29/7, 2015 at 10:53 Comment(6)
Thanks Avinash, but I'm using Picasso to load images so image loading takes place asynchronously in the background. And Picasso takes care of caching.Radicalism
Appreciate your answer. But in my case I am loading bitmaps in asynctask and using memory cache as well to save loaded images.Sticky
If you have n fragments set this option in for your viewPager: viewPager.setOffscreenPageLimit(n). It seems like your fragments are being recreated each time. This causes your viewCreated to be called, thus calling your parse(). I have updated my answer using this option (mViewPager.setOffscreenPageLimit(strings.size()); Please check. Hope this might help.Boleslaw
Thanks Avinash, but this is not a good solution. The main reason being I would never want to store all my Fragments in memory, as I could have upwards of 50, and they're complex Fragments. Secondly, it just doesn't work.Radicalism
@KarimVarela : Thanks for the comment,is it possible for you to post the sample code here.Boleslaw
Sample code posted for Activity and Adapter. Sorry, it's a lot.Radicalism
C
1

In my case, the problem was an empty Fragment. After create a fragment with a layout, it's starts working as expected.

Basically, I used a empty fragment for test the view:

fragment = new Fragment(); //Strange behavior in ViewPager

When I used the final fragment which has a layout, the behavior was the correct:

fragment = MyFragment.newInstance(); //Correct behavior

I know that this response doesn't answer the current question, but some people with similar problems arrive here. So, I hope it's helpful.

Crin answered 17/10, 2019 at 11:52 Comment(0)
S
0

For now, I am thinking there is an issue with the width of the layout. So far I only see one suspect, there is an unknown attribute

app:aspectRatio="@integer/photo_ratio_height_over_width" 

in UI element <com.coffeemeetsbagel.views.CustomAsShitViewPager...

Apparently there is a custom attribute in library/code CustomAsShitViewPager, perhaps post the code related to aspectRatio, at least.

Stingy answered 4/8, 2015 at 7:35 Comment(2)
Thanks, I tried using a stock ViewPager, but it didn't help the issue.Radicalism
@KarimVarela, If I understand right, you have used the Google/Android libraries only in the layouts, and you still have the same problem. This tells me the layout (for each fragment, fragtest.xml I believe) is the issue. Perhaps debug the layout by isolating each UI element, one at a time if need be. I'll review it again.Stingy
S
0

I just realized that you're doing a lot of UI work in onCreate() in the main Activity. It is more proper to do the work in onCreateView(). I believe the Android framework is not finish doing the UI work in onCreate() and therefore you see incomplete UI rendering. I know this is not clearly stated in Android documentation. If you check other SO posts or sample projects, other developers do little UI work in onCreate(). At least, the layouts are simpler than yours.

Here is my suggestion. Inflate the fragtest layout in an Activity or Fragment in method onCreateView(), using the ID listed on post. Notice the override method only inflates. Sample code:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
   return inflater.inflate(R.layout.fragtest, container, false);
}

On a Fragment, start accessing the UI elements and the ViewPager, using the ID listed on post. Sample code:

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
   mProfilesViewPager = (ViewPager) findViewById(R.id.viewPager);
   ...
Stingy answered 5/8, 2015 at 8:54 Comment(4)
Thanks, but the views for each Fragment that I'm swiping between have already been created and stored in memory. The FragmentStatePagerAdapter does that for me. There's also no onViewCreated() method for Activities, not that it would matter, however, as the Activity's view was created long before this problem is evident.Radicalism
@KarimVarela, In your comment "each Fragment that I'm swiping between have already been created and stored in memory", yes that is true. But during the Fragment lifecycle, onCreateView() for one example is triggered when user swipes between fragments. I believe sometimes views are NOT completely finished, especially when layouts are somewhat complicated.Stingy
@KarimVarela, You're right about onViewCreated() method is not available for Activity, just Fragment. I corrected my post on it. Another interesting info, I used PagerAdapter in my app, and it does not store fragments. It detects tabs from override method instantiateItem(). Overall, I think your issue relates to fragments lifecycle and UI.Stingy
I'm pretty sure onCreateView() is only called once per lifetime of a Fragment.Radicalism
T
0

Extremely late reply, but if someone is having trouble, this is what worked for me. I was wrapping the ViewPager inside View with custom styling like this:

<View style={{bla bla bla style here}}>
<ViewPager>
<Layout></Layout>
<Layout></Layout>
</ViewPager>
</View>

I just removed <View> from the file and ViewPager fixed itself.

<ViewPager>
<Layout></Layout>
<Layout></Layout>
</ViewPager>

so what I am saying is, try removing some other parent layout tags, maybe they are causing this problem.

PS: this worked in react-native but I hope it helps in other realms as well.

Teena answered 3/12, 2019 at 17:39 Comment(0)
L
0

I also encountered this problem, my solution below:

 mainViewPager.post { mainViewPager.setCurrentItem(item, false) }
Lichenin answered 19/1, 2021 at 15:17 Comment(0)
H
0

Disabling animations and setting the ViewPager's position manually didn't work for me. In my situation, the bug happened sometimes when navigating to another screen and going back. What I ended up doing was saving the horizontal offset, via a OnPageChangeListener, and "resetting" it to zero in the activity's onResume() if it wasn't zero.

private var lastOffset = 0.0F

override fun onCreate() {
    viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int
            ) {
                lastOffset = positionOffset
            }

            // other listener methods
        }
        )
}

override fun onResume() {
    if (lastOffset != 0.0F) {
        viewPager.beginFakeDrag()
        viewPager.fakeDragBy(lastOffset)
        viewPager.endFakeDrag()
    }

}
Habergeon answered 11/5, 2022 at 14:20 Comment(0)
S
-1

For now, I suspect two methods.

1) In the fragment code:

@Override
public int getCount()
{
    return mBagels.size();
}

Note: getCount() should return the number of fragments instead of the size of the list. I could not tell how many of fragments you will have. Perhaps you have to keep track in the Adapter.

2) Another, I suspect getItem() method and the use of newInstance(). The related specific code:

FragmentProfile fragment = FragmentProfile.newInstance(mBagels.get(i),...

Notes:

  • According to Google webpage FragmentStatePagerAdapter, method newInstance should create a new fragment probably because that's when the FragmentStatePagerAdapter does not have the fragment in memory yet OR was released from memory.

  • Perhaps post the code related to FragmentProfile.newInstance especially if you disagree with my statement above.

Stingy answered 5/8, 2015 at 18:41 Comment(3)
Thank you. (1) The number of Fragments could change as the size of list mBagels changes, so it must be tied to mBagels.size().Radicalism
(2) FragmentProfile.newInstance() just creates a new FragmentProfile. It should only be called by the FragmentStatePagerAdapter if that item is not currently in memory.Radicalism
I can't add anything more to my post, but here's FragmentProfile.newInstance(): public static FragmentProfile newInstance(Bagel bagel, boolean isInAppChatProfile, UserProfile myProfile) { Logger.d("ENTER"); FragmentProfile f = new FragmentProfile(); Bundle args = new Bundle(); args.putSerializable(EXTRA_THE_BAGEL, bagel); args.putBoolean(EXTRA_IS_IN_APP_CHAT_PROFILE, isInAppChatProfile); args.putSerializable(EXTRA_MY_PROFILE, myProfile); f.setArguments(args); return f; }Radicalism

© 2022 - 2024 — McMap. All rights reserved.