安卓:ViewPager在视图之间卡住了

22
我有一个ViewPager,可以在不同Fragment之间滑动。我使用了FragmentStatePagerAdapter来将这些Fragments提供给ViewPager。如果用户以正常的速度向左滑动,然后快速向右滑动,他们可能会使ViewPager进入一种奇怪的状态,显示多个Fragments。
例如,如果用户在Fragment A上,然后以正常的速度向左滑动到Fragment B,然后快速向右滑动返回到Fragment A,则屏幕上会显示两个Fragments A和B。
有人知道为什么会发生这种情况或者有好的方法来防止它吗?
以下是它的样子: enter image description here 以下是在XML中定义ViewPager的代码:
<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"/>

另外,我记录了 onPageChangeListener() 的输出并注意到当 ViewPager 在视图之间卡住时,它报告了一个 positionOffset 值为 0。以下是 ViewPager 在从 STATE_DRAGGING 过渡到 STATE_SETTLING 再到落入这种奇怪状态时的值:
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
因此,看起来 ViewPager 向我返回了错误的 positionOffset 值。
完整的示例代码 Activity 和 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();
    }
}
}

以下是每个Fragment的XML布局代码(由于SO字符限制,必须删减一些内容):

<?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">

...


我和你一样遇到了同样的问题,仍在等待回复。顺便问一下,你的活动是否在UI线程上做了很多工作?因为这似乎是卡住的可能原因。我的线程在这里:http://stackoverflow.com/questions/31537039/viewpager-do-not-behave-elastically-or-get-stuck-in-views - user1460340
@Gaurav:我和Karim在同一个团队工作,所以我可以说,除了解析大型XML文件外,我们在UI线程上没有做任何非常昂贵/长时间运行的操作。即使如此,这也不应该太重要,因为这些视图已经由viewpager构建好了,我们只是将它们移动到屏幕上或离开屏幕... - Archit
是的,完全同意。我注意到如果我通过setCurrentItem()更改viewpager,一切都很正常,动画也非常流畅。唯一的问题是手动滑动时。 - user1460340
由于您正在使用ViewPager,因此应在其中使用布局。请发布布局和相关代码。时间不多了。 - The Original Android
为每个Fragment添加了布局,但无法全部适配。 - Karim Varela
显示剩余4条评论
9个回答

21

我注意到如果我的应用程序使用了 animateLayoutChanges 动画,就会出现这个问题。在xml文件中禁用它可以防止页面在中间停滞不前。


对我来说有效。但现在我将必须应用动画以实现视图可见性的平滑过渡。 - user1517153
应该接受作为正确答案。让我们帮助其他用户快速找到正确的建议! - Максим Петлюк
2
移除animateLayoutChanges后,这个问题就解决了。显然这是一个时间问题 - 当你快速滑动时,动画还没有完成,它会使得一些不应该显示在屏幕上的视图出现。 - DiscDev
是的,似乎任何类型的动画都可以做到这一点。 - Kirill Starostin

3

尝试以下示例代码,并根据您的要求进行修改(我猜测您正在主UI线程上加载图像而不是缓存它,这只是一个猜测)。在此代码中,我正在从互联网下载和缓存图像:

创建名为SomeFragTest的Activity类。

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);

        }
    }
}
   }
}

创建名为activity_layout的xml文件,用于此项任务。
<?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>

现在我们已经创建了要在ViewPager中填充的Fragment类: 创建一个名为ChildFrag的类,如下所示。
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);
}
}

现在我们已经创建了名为fragtest的片段XML:
 <?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>

请在 AndroidManifest.xml 文件中添加以下权限:
 <uses-permission android:name="android.permission.INTERNET" />

谢谢Avinash,但我正在使用Picasso来加载图像,因此图像加载是在后台异步进行的。而且Picasso会负责缓存。 - Karim Varela
感谢您的答复。但在我的情况下,我正在使用asynctask加载位图,并使用内存缓存来保存已加载的图像。 - user1460340
如果您有n个片段,请在viewPager中设置此选项:viewPager.setOffscreenPageLimit(n)。看起来您的片段每次都会被重新创建。这会导致调用您的viewCreated,从而调用您的parse()。我已经使用此选项更新了我的答案(mViewPager.setOffscreenPageLimit(strings.size())。请检查。希望这可以帮助到您。 - avinash
谢谢Avinash,但这不是一个好的解决方案。主要原因是我永远不想将所有的Fragment存储在内存中,因为我可能会有50个以上,并且它们是复杂的Fragment。其次,它根本不起作用。 - Karim Varela
@KarimVarela:感谢您的评论,您能否在这里发布示例代码? - avinash
为Activity和Adapter发布了示例代码。抱歉,有点多。 - Karim Varela

1
在我的情况下,问题是一个空的Fragment。创建一个带有布局的Fragment后,它开始按预期工作。
基本上,我使用了一个空的Fragment来测试视图:
fragment = new Fragment(); //Strange behavior in ViewPager

当我使用具有布局的最终片段时,行为是正确的:
fragment = MyFragment.newInstance(); //Correct behavior

我知道这个回答并没有解答当前的问题,但是有些遇到类似问题的人会来到这里。因此我希望这个回答能够有所帮助。


0

禁用动画并手动设置ViewPager的位置对我没有起作用。在我的情况下,当导航到另一个屏幕并返回时,有时会出现错误。最终我做的是通过OnPageChangeListener保存水平偏移量,并在活动的onResume()中将其“重置”为零(如果不为零)。

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()
    }

}

0

我也遇到了这个问题,我的解决方案如下:

 mainViewPager.post { mainViewPager.setCurrentItem(item, false) }

0
我刚意识到你在主Activity的onCreate()方法中做了很多UI工作。更合适的方式是在onCreateView()方法中完成这些工作。我相信Android框架没有在onCreate()方法中完成UI工作,因此你会看到不完整的UI渲染。
我知道这在Android文档中没有明确说明。如果你查看其他SO帖子或示例项目,其他开发人员在onCreate()方法中只做少量的UI工作。至少,布局比你的简单。
这是我的建议。 在一个Activity或Fragment的onCreateView()方法中膨胀fragtest布局,使用帖子上列出的ID。请注意,重写方法只膨胀。 示例代码:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
   return inflater.inflate(R.layout.fragtest, container, false);
}

在一个Fragment中,可以通过帖子上列出的ID访问UI元素和ViewPager。示例代码:
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
   mProfilesViewPager = (ViewPager) findViewById(R.id.viewPager);
   ...

谢谢,但我要滑动的每个Fragment的视图已经被创建并存储在内存中了。FragmentStatePagerAdapter已经为我完成了这个任务。另外,Activities没有onViewCreated()方法,即使有也不会有影响,因为Activity的视图早在出现这个问题之前就已经被创建了。 - Karim Varela
@KarimVarela,在你的评论中提到:“我要滑动的每个片段已经被创建并存储在内存中”,是的,那是正确的。但是在Fragment生命周期中,例如onCreateView()会在用户滑动片段时触发。我相信有时视图尚未完全完成,尤其是当布局有些复杂时。 - The Original Android
@KarimVarela,你说的没错,onViewCreated()方法只对Fragment有效,对Activity无效。我已经在帖子中进行了更正。另外一个有趣的信息是:我在我的应用程序中使用了PagerAdapter,它不会存储片段。它从覆盖方法instantiateItem()中检测选项卡。总的来说,我认为你的问题与片段的生命周期和UI有关。 - The Original Android
我非常确定onCreateView()方法在Fragment的生命周期中只会被调用一次。 - Karim Varela

0

目前,我认为布局的宽度存在问题。到目前为止,我只发现一个嫌疑人,即存在一个未知属性。

app:aspectRatio="@integer/photo_ratio_height_over_width" 

在UI元素<com.coffeemeetsbagel.views.CustomAsShitViewPager...

显然,在库/代码CustomAsShitViewPager中有一个自定义属性,可能至少要发布与aspectRatio相关的代码。


谢谢,我尝试使用了一个普通的ViewPager,但它并没有解决这个问题。 - Karim Varela
@KarimVarela,如果我理解正确的话,您只在布局中使用了Google/Android库,但仍然存在相同的问题。这告诉我布局(对于每个片段,我相信是fragtest.xml)是有问题的。也许需要逐个隔离每个UI元素来调试布局,如果需要的话。我会再次审查它。 - The Original Android

0
非常晚的回复,但如果有人遇到问题,这是对我有效的解决方法。 我将ViewPager包装在具有自定义样式的View中,就像这样:
<View style={{bla bla bla style here}}>
<ViewPager>
<Layout></Layout>
<Layout></Layout>
</ViewPager>
</View>

我刚刚从文件中删除了<View>,ViewPager就自己修复了。

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

我的意思是,尝试移除一些其他的父级布局标签,也许它们导致了这个问题。

PS:这在React Native中有效,但我希望它在其他领域也有所帮助。


-1

目前,我怀疑有两种方法。

1)在片段代码中:

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

注意:`getCount()`应返回片段的数量而不是列表的大小。我无法告诉你会有多少片段。也许您需要在适配器中跟踪。
2) 另一个问题,我怀疑`getItem()`方法和`newInstance`的使用。相关的具体代码:
FragmentProfile fragment = FragmentProfile.newInstance(mBagels.get(i),...

注意:

  • 根据Google网页FragmentStatePagerAdapter,方法newInstance应该创建一个新的片段,可能是因为此时FragmentStatePagerAdapter在内存中没有该片段,或者已经从内存中释放。

  • 如果您不同意我上面的说法,也许可以发布与FragmentProfile.newInstance相关的代码。


谢谢。 (1) Fragments的数量可能随着列表mBagels的大小而改变,因此它必须与mBagels.size()相绑定。 - Karim Varela
(2) FragmentProfile.newInstance() 只是创建一个新的 FragmentProfile。它应该只在该项当前不在内存中时由 FragmentStatePagerAdapter 调用。 - Karim Varela
我无法在我的帖子中添加更多内容,但这是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; } - Karim Varela

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接