SwipeRefreshLayout мешает ScrollView в ViewPager

У меня есть SwipeRefreshLayout вокруг ViewPager. Фрагменты, загруженные ViewPager, содержат ScrollViews. Когда я прокручиваю вниз, он работает нормально, но когда я прокручиваю вверх, активируется SwipeRefreshLayout, не позволяя сначала прокрутить ScrollView вверх. Как я могу убедиться, что ScrollView имеет приоритет?

Это работало нормально, когда SwipeRefreshLayout находился в отдельных фрагментах сразу за пределами ScrollView, но мне нужно, чтобы он был в самом действии вокруг ViewPager.

Вот соответствующие макеты:

Мероприятия:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#EEEEEE"
    android:fitsSystemWindows="true">

    <include layout="@layout/toolbar" />

    <io.karim.MaterialTabs
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:background="@color/primary"
        app:mtIndicatorColor="@color/icons"
        app:mtIndicatorHeight="2dp"
        app:mtSameWeightTabs="true"
        app:mtPaddingMiddle="false"
        app:mtTextColorSelected="@color/icons"
        android:textColor="@color/icons"
        android:elevation="4dp"
        tools:ignore="UnusedAttribute" />

    <android.support.v4.widget.SwipeRefreshLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/swipeRefresh"
        android:layout_height="wrap_content"
        android:layout_width="match_parent">

        <android.support.v4.view.ViewPager
            android:id="@+id/pager"
            android:layout_width="match_parent"
            android:layout_height="0dp" />

    </android.support.v4.widget.SwipeRefreshLayout>

</LinearLayout>

Фрагмент, загруженный ViewPager (есть 5 вкладок, каждая с одной из них):

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="me.sargunvohra.android.purduediningcourts.presenter.MenuFragment">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:paddingLeft="@dimen/activity_horizontal_margin"
            android:paddingRight="@dimen/activity_horizontal_margin"
            android:paddingStart="@dimen/activity_horizontal_margin"
            android:paddingEnd="@dimen/activity_horizontal_margin"
            android:paddingTop="@dimen/activity_vertical_margin"
            android:paddingBottom="@dimen/activity_vertical_margin"
            >

            <!-- Content snipped for brevity -->

            <!-- blah blah... lots of content here -->

        </LinearLayout>

</ScrollView>

И ЗДЕСЬ находится репозиторий на GitHub, если вам нужна дополнительная информация:




Ответы (3)


Google решает эту проблему с помощью примера, опубликованного в проекте github google-sample.

Создайте представление, расширяющее исходный SwipeRefreshLayout:

/*
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.swiperefreshmultipleviews;

import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.view.View;
import android.widget.AbsListView;

/**
 * A descendant of {@link android.support.v4.widget.SwipeRefreshLayout} which supports multiple
 * child views triggering a refresh gesture. You set the views which can trigger the gesture via
 * {@link #setSwipeableChildren(int...)}, providing it the child ids.
 */
public class MultiSwipeRefreshLayout extends SwipeRefreshLayout {

    private View[] mSwipeableChildren;

    public MultiSwipeRefreshLayout(Context context) {
        super(context);
    }

    public MultiSwipeRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * Set the children which can trigger a refresh by swiping down when they are visible. These
     * views need to be a descendant of this view.
     */
    public void setSwipeableChildren(final int... ids) {
        assert ids != null;

        // Iterate through the ids and find the Views
        mSwipeableChildren = new View[ids.length];
        for (int i = 0; i < ids.length; i++) {
            mSwipeableChildren[i] = findViewById(ids[i]);
        }
    }

    // BEGIN_INCLUDE(can_child_scroll_up)
    /**
     * This method controls when the swipe-to-refresh gesture is triggered. By returning false here
     * we are signifying that the view is in a state where a refresh gesture can start.
     *
     * <p>As {@link android.support.v4.widget.SwipeRefreshLayout} only supports one direct child by
     * default, we need to manually iterate through our swipeable children to see if any are in a
     * state to trigger the gesture. If so we return false to start the gesture.
     */
    @Override
    public boolean canChildScrollUp() {
        if (mSwipeableChildren != null && mSwipeableChildren.length > 0) {
            // Iterate through the scrollable children and check if any of them can not scroll up
            for (View view : mSwipeableChildren) {
                if (view != null && view.isShown() && !canViewScrollUp(view)) {
                    // If the view is shown, and can not scroll upwards, return false and start the
                    // gesture.
                    return false;
                }
            }
        }
        return true;
    }
    // END_INCLUDE(can_child_scroll_up)

    // BEGIN_INCLUDE(can_view_scroll_up)
    /**
     * Utility method to check whether a {@link View} can scroll up from it's current position.
     * Handles platform version differences, providing backwards compatible functionality where
     * needed.
     */
    private static boolean canViewScrollUp(View view) {
        if (android.os.Build.VERSION.SDK_INT >= 14) {
            // For ICS and above we can call canScrollVertically() to determine this
            return ViewCompat.canScrollVertically(view, -1);
        } else {
            if (view instanceof AbsListView) {
                // Pre-ICS we need to manually check the first visible item and the child view's top
                // value
                final AbsListView listView = (AbsListView) view;
                return listView.getChildCount() > 0 &&
                        (listView.getFirstVisiblePosition() > 0
                                || listView.getChildAt(0).getTop() < listView.getPaddingTop());
            } else {
                // For all other view types we just check the getScrollY() value
                return view.getScrollY() > 0;
            }
        }
    }
    // END_INCLUDE(can_view_scroll_up)
}

Используйте это представление в макете xml:

<com.example.android.swiperefreshmultipleviews.MultiSwipeRefreshLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      android:id="@+id/swiperefresh"
      android:layout_width="match_parent"
      android:layout_height="match_parent">

    <!-- your code -->

</com.example.android.swiperefreshmultipleviews.MultiSwipeRefreshLayout>

И в вашей деятельности или фрагменте вы можете использовать этот компонент с:

mSwipeRefreshLayout = (MultiSwipeRefreshLayout) view.findViewById(R.id.swiperefresh);
mSwipeRefreshLayout.setSwipeableChildren(android.R.id.list, android.R.id.empty);

android.R.id.list и android.R.id.empty — это идентификаторы для вашего списка или представления ресайклера. Представление отлично работает с двумя списками с одинаковыми идентификаторами.

Вы можете увидеть реальный пример в проекте google-sample github.

person Johann67    schedule 31.03.2016

Замените свой ScrollView на NestedScrollView поведение прокрутки будет работать так, как вы ожидаете.

person sagar.android    schedule 24.08.2017
comment
NestedScrollView устарел. Пожалуйста, обновите свой ответ или удалите его. Это решение работает, но Google иногда может удалить этот класс. - person JonasPTFL; 29.02.2020
comment
Исправление, ссылка устарела, правда, но она была перенесена на AndroidX: NestedScrollView - person jschlepp; 19.06.2020
comment
как сказал @jschlepp, используйте androidx.core.widget.NestedScrollView - person SqAR.org; 14.10.2020

Я решил проблему. Я создал SwipeRefreshLayout следующим образом:

public class MenuSwipeRefreshLayout: SwipeRefreshLayout {

    public var target: View? = null

    constructor(ctx: Context): super(ctx)
    constructor(ctx: Context, attr: AttributeSet): super(ctx, attr)

    override fun canChildScrollUp(): Boolean {
        return target?.canScrollVertically(-1) ?: super.canChildScrollUp()
    }
}

и я устанавливаю цель на видимый ScrollView каждый раз, когда ViewPager меняет представление.

Кстати, это Kotlin, а не Java.

person sargunv    schedule 05.07.2015