SwipeRefreshLayout + WebView, когда позиция прокрутки вверху

Я пытаюсь использовать SwipeRefreshLayout с WebView.

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

Как сделать так, чтобы событие обновления происходило только тогда, когда позиция прокрутки веб-просмотра находится сверху. (то есть он смотрит на верхнюю часть страницы)?

17 ответов

Мне удалось решить это без необходимости что-либо расширять. Посмотрите на этот фрагмент (специфичный для фрагмента):

private ViewTreeObserver.OnScrollChangedListener mOnScrollChangedListener;

@Override
public void onStart() {
    super.onStart();

    swipeLayout.getViewTreeObserver().addOnScrollChangedListener(mOnScrollChangedListener =
            new ViewTreeObserver.OnScrollChangedListener() {
                @Override
                public void onScrollChanged() {
                    if (mWebView.getScrollY() == 0)
                        swipeLayout.setEnabled(true);
                    else
                        swipeLayout.setEnabled(false);

                }
            });
}

@Override
public void onStop() {
    swipeLayout.getViewTreeObserver().removeOnScrollChangedListener(mOnScrollChangedListener);
    super.onStop();
}

Для более широкого контекста взгляните на мой ответ для Android - SwipeRefreshLayout с пустым textview.

Я думаю, что рекомендуемый способ сделать это, чтобы расширить SwipeRefreshLayout и переопределить canChildScrollUp() - https://developer.android.com/reference/android/support/v4/widget/SwipeRefreshLayout.html

Вот как это делается в приложении Google IO 2014 Schedule - https://github.com/google/iosched/blob/master/android/src/main/java/com/google/samples/apps/iosched/ui/widget/MultiSwipeRefreshLayout.java#L76

CustomSwipeRefreshLayout вероятно будет выглядеть так:

public class CustomSwipeRefreshLayout extends SwipeRefreshLayout {

    private CanChildScrollUpCallback mCanChildScrollUpCallback;

    public interface CanChildScrollUpCallback {
        boolean canSwipeRefreshChildScrollUp();
    }

    public void setCanChildScrollUpCallback(CanChildScrollUpCallback canChildScrollUpCallback) {
        mCanChildScrollUpCallback = canChildScrollUpCallback;
    }

    @Override
    public boolean canChildScrollUp() {
        if (mCanChildScrollUpCallback != null) {
            return mCanChildScrollUpCallback.canSwipeRefreshChildScrollUp();
        }
        return super.canChildScrollUp();
    }
}

И в твоем Activity это имеет WebView,

public class FooActivity
        implements CustomSwipeRefreshLayout.CanChildScrollUpCallback {

    private CustomSwipeRefreshLayout mRefreshLayout;
    private WebView mWebView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // after initialization
        mRefreshLayout.setCanChildScrollUpCallback(this);
    }

    @Override
    public boolean canSwipeRefreshChildScrollUp() {
        return mWebview.getScrollY() > 0;
    }
}

Просто реализуй свой Fragment или же Activity с ViewTreeObserver.OnScrollChangedListener затем установите Listener как webview.getViewTreeObserver().addOnScrollChangedListener(this);

В onScrollChanged() метод сделать так

    @Override
    public void onScrollChanged() {
        if (webview.getScrollY() == 0) {
            swipeLayout.setEnabled(true);
        } else {
            swipeLayout.setEnabled(false);
        }
    }

Самые последние версии библиотеки поддержки имеют SwipeRefreshLayout#setOnChildScrollUpCallback метод, который позволяет избежать подклассов SwipeRefreshLayout,

mBinding.swipeRefreshLayout.setOnChildScrollUpCallback((parent, child) -> 
            mBinding.webView.getScrollY() > 10);

Вы можете попробовать это:

Он работает для API>= 23 Android Marshmallow

      webView.setOnScrollChangeListener(new View.OnScrollChangeListener() {
        @Override
        public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
            swipeRefreshLayout.setEnabled(scrollY == 0);
        }
    });

Изменить 1: это не работает, если веб-страница имеет вложенный прокручиваемый контент. Поэтому вместо SwipeRefreshLayout я решил использовать простой ImageButton для перезагрузки веб-страницы.

Вы можете решить эту проблему, расширив SwipeRefreshLayout с помощью пользовательского SwipeRefreshLayout, который принимает WebView и использует WebView для проверки позиции scrollY.

Примечание. Это будет работать для прокрутки всей страницы, но может не работать с вложенными прокрутками. Также, если у вас горизонтальная прокрутка, вы также можете добавить логику, найденную в этом ответе: /questions/9253698/horizontalscrollview-vnutri-swiperefreshlayout/9253717#9253717

public class CustomSwipeToRefresh extends SwipeRefreshLayout {

    private final int yScrollBuffer = 5;
    private WebView mWebView;

    public CustomSwipeToRefresh(Context context, WebView webView) {
        super(context);

        mWebView = webView;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        if (mWebView.getScrollY() > yScrollBuffer)
            return false;

        return super.onInterceptTouchEvent(event);
    }
}

Я отвечаю на тот же вопрос, но использую Kotlin. Включая swipeRefreshLayout когда свиток webView получает 0:

Сделайте эту переменную в вашем фрагменте или деятельности:

private val onScrollChangedListener = ViewTreeObserver.OnScrollChangedListener{ swipeRefreshLayout.isEnabled = webView.scrollY == 0 }

затем onViewCreated:

swipeRefreshLayout.viewTreeObserver.addOnScrollChangedListener(onScrollChangedListener)

затем onStop:

swipeRefreshLayout.viewTreeObserver.removeOnScrollChangedListener(onScrollChangedListener)

Отсюда все будет хорошо работать. Но если используемая вами веб-страница имеет фиксированный заголовок, который не прокручивается scrollY всегда будет равен 0 , И это решение вместе с другим решением на этой странице может не работать.

Я столкнулся с аналогичной проблемой, но ни один из ответов, приведенных здесь, не работает для меня. Итак, я получил решение из этого ответа.

Шаг 1: Создать класс CustomWebView

public class CustomWebView extends WebView{

private OnScrollChangedCallback mOnScrollChangedCallback;

public CustomWebView(final Context context)
{
    super(context);
}

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

public CustomWebView(final Context context, final AttributeSet attrs, final int defStyle)
{
    super(context, attrs, defStyle);
}

@Override
protected void onScrollChanged(final int l, final int t, final int oldl, final int oldt)
{
    super.onScrollChanged(l, t, oldl, oldt);
    if(mOnScrollChangedCallback != null) mOnScrollChangedCallback.onScroll(l, t);
}

public OnScrollChangedCallback getOnScrollChangedCallback()
{
    return mOnScrollChangedCallback;
}

public void setOnScrollChangedCallback(final OnScrollChangedCallback onScrollChangedCallback)
{
    mOnScrollChangedCallback = onScrollChangedCallback;
}

/**
 * Impliment in the activity/fragment/view that you want to listen to the webview
 */
public static interface OnScrollChangedCallback
{
    public void onScroll(int horizontal, int vertical);
}}

Шаг 2: Использование XML- файла webview как это-

 <com.yourpackagename.CustomWebView
        android:id="@+id/webview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

Шаг 3: В вашем MainActivity использование setOnScrollChangedCallback -

  mWebView.setOnScrollChangedCallback(new CustomWebView.OnScrollChangedCallback(){
        public void onScroll(int horizontal, int vertical){
            System.out.println("=="+horizontal+"---"+vertical);
   //this is to check webview scroll behaviour
            if (vertical<50){
                swipeRefreshLayout.setEnabled(true);
            }else{
                swipeRefreshLayout.setEnabled(false);
            }
        }
    });
}
// swipe - active only when WebView Y==0        
swipeView.getViewTreeObserver().addOnScrollChangedListener(new ViewTreeObserver.OnScrollChangedListener() {
            @Override
            public void onScrollChanged() {
                if (webView.getScrollY() == 0) {
                    Log.d(TAG, "onScrollChanged - y=0 - make swipe refresh active");
                    swipeView.setEnabled(true);
                }else {
                    swipeView.setEnabled(false);
                    Log.d(TAG, "onScrollChanged - y is not null - swipe refresh not active");
                }
            }
        });

// swipe - after refresh do work (see above for condition)
    swipeView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {

        @Override
        public void onRefresh() {

            Log.d(TAG, "Swipe - refresh");

            // do work ... your work method here
            getSchedule();

            // hide spinner of swiperefreshlayout
            swipeView.setRefreshing(false);

        }

    });

Самый надежный подход к разрешению конфликта между SwipeRefreshLayout и WebView- привязать жест касания / смахивания к определенному месту в верхней части экрана.

Предыдущие ответы основаны на обнаружении webview.scrollY может быть ненадежным, потому что некоторые страницы имеют фиксированное положение заголовка, поэтому даже при наличии прокручиваемого списка webview.scrollY всегда будет возвращать 0

ИСТОЧНИК

  1. Создать индивидуальный SwipeRefreshLayout
class MySwipeRefreshLayout @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : SwipeRefreshLayout(context, attrs) {

    private var mDeclined = false
    private var mPrevY: Float = 0f

    override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                mPrevY = event.y

                mDeclined = false
                if (mPrevY > resources.getDimension(R.dimen.disable_swipe_after)){
                    mDeclined = true
                }
            }
            MotionEvent.ACTION_MOVE -> {
                if (mDeclined) {
                    return false
                }
            }
        }
        return super.onInterceptTouchEvent(event)
    }
}
  1. Применить его в файле просмотра
    <yourpackage.MyLimitedSwipeRefreshLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        >

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:windowSoftInputMode="adjustResize"
            >

            <WebView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                />
        </FrameLayout>
    </yourpackage.MySwipeRefreshLayout>

Этот фрагмент кода Kotlin настраивает SwipeRefreshLayout, чтобы включить функцию обновления по запросу для WebView в приложении Android. Он проверяет положение прокрутки WebView и включает или отключает действие обновления в зависимости от того, прокручивается ли WebView вверх. Когда пользователь инициирует обновление, он перезагружает содержимое WebView и останавливает анимацию обновления по завершении.

          binding.swipeRefreshLayout.viewTreeObserver.addOnScrollChangedListener {
        binding.swipeRefreshLayout.isEnabled = binding.webView.scrollY === 0
    }

    binding.swipeRefreshLayout.setOnRefreshListener {
        binding.swipeRefreshLayout.isRefreshing = false
        binding.webView.reload()
    }

Меня устраивает :

      swipeRefreshLayout.getViewTreeObserver().addOnScrollChangedListener(() -> {
    swipeRefreshLayout.setEnabled(webView.getScrollY() == 0);
});

У меня ничего из вышеперечисленного не работало. Вот мой подход

 @Override
 public boolean dispatchTouchEvent(MotionEvent event)
 {
super.dispatchTouchEvent(event);
int x = (int)event.getX();
int y = (int)event.getY();
 if(y>800){
     swipeRefreshLayout.setEnabled(false);
 }else {
     swipeRefreshLayout.setEnabled(true);
 }
 Log.e("yy",""+y);

switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
    case MotionEvent.ACTION_MOVE:
    case MotionEvent.ACTION_UP:
}

return false;

}

Ответ, предоставленный hiBrianLee, почти сработал для меня, но, как писал Джон Шелли, getScrollY всегда возвращал 0 в моем случае (я использовал ListView как вложенный прокручиваемый ребенок). Что сразу сработало, так это использование canScrollVertically вместо этого (который является View метод, поэтому он доступен как для ListViewс и WebViewс, а также для любого другого вида прокрутки ребенка).

Мой класс макета выглядит следующим образом:

public class NestedScrollSwipeLayout extends SwipeRefreshLayout {

    private ListView scrollChild;

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

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

    public void setScrollChild(ListView child) {
        this.scrollChild = child;
    }

    @Override
    public boolean canChildScrollUp() {
        return (scrollChild != null && scrollChild.canScrollVertically(-1)) ||
            super.canChildScrollUp();
    }
}

(Как уже упоминалось, canScrollVertically это View метод, поэтому ListView может быть безопасно заменено общим View или любой другой специализированный класс представления.)

Я обнаружил, что отключение вложенной прокрутки в веб-представлении (не самого SwipeRefreshLayout) решило эту проблему для меня. Это не будет возможно сделать во всех ситуациях, но было в моей.

webView.setNestedScrollingEnabled(false);

Я нашел решение для этого. Просто используйте CoordinatorLayout в качестве корневого макета и добавьте layout_behavior="@string/appbar_scrolling_view_behavior" в свой SwipeRefreshLayout. Это идеально для меня.

Используйте SwipeRefreshLayout как обычно, как и в других представлениях, но включите вложенную прокрутку.

swipeRefreshLayout.setNestedScrollingEnabled(true);
Другие вопросы по тегам