blob: 60fda4a71e7858e3870254bf5a090614eb53beb4 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.gesturenav;
import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.FrameLayout;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.tab.Tab;
/**
* FrameLayout that supports side-wise slide gesture for history navigation. Inheriting
* class may need to override {@link #wasLastSideSwipeGestureConsumed()} if
* {@link #onTouchEvent} cannot be relied upon to know whether the side-swipe related
* event was handled. Namely {@link android.support.v7.widget.RecyclerView}) always
* claims to handle touch events.
*/
public class HistoryNavigationLayout extends FrameLayout {
private final boolean mDelegateSwipes;
private final boolean mNavigationEnabled;
private GestureDetector mDetector;
private NavigationHandler mNavigationHandler;
public HistoryNavigationLayout(Context context) {
this(context, null);
}
public HistoryNavigationLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mDelegateSwipes = ChromeFeatureList.isEnabled(ChromeFeatureList.DELEGATE_OVERSCROLL_SWIPES);
mNavigationEnabled =
ChromeFeatureList.isEnabled(ChromeFeatureList.OVERSCROLL_HISTORY_NAVIGATION)
&& (context instanceof ChromeActivity);
}
/**
* Sets the tab which the native page for this layout is running on.
* @param tab Tab instance.
* TODO(jinsukkim): Look into replacing tab with an interface which will make this a pure
* UI class. Then navigation tests will also be a pure UI one using a dummy activity.
*/
public void setTab(Tab tab) {
if (!mNavigationEnabled) return;
mDetector = new GestureDetector(getContext(), new SideNavGestureListener());
mNavigationHandler = new NavigationHandler(this, () -> tab);
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mNavigationEnabled) mNavigationHandler.reset();
}
@Override
public boolean dispatchTouchEvent(MotionEvent e) {
if (mDetector != null) {
mDetector.onTouchEvent(e);
mNavigationHandler.onTouchEvent(e.getAction());
}
return super.dispatchTouchEvent(e);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
// Do not propagate touch events down to children if navigation UI was triggered.
if (mDetector != null && !mDelegateSwipes && mNavigationHandler.isActive()) return true;
return super.onInterceptTouchEvent(e);
}
private class SideNavGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDown(MotionEvent event) {
return mNavigationHandler.onDown();
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
// |onScroll| needs handling only after the state moves away from |NONE|. This helps
// invoke |wasLastSideSwipeGestureConsumed| which may be expensive less often.
if (mNavigationHandler.isStopped()) return true;
if (mDelegateSwipes && wasLastSideSwipeGestureConsumed()) {
mNavigationHandler.reset();
return true;
}
return mNavigationHandler.onScroll(
e1.getX(), distanceX, distanceY, e2.getX(), e2.getY());
}
}
/**
* Checks if the gesture event was consumed by one of children views, in which case
* history navigation should not proceed. Whatever the child view does with the gesture
* events should take precedence and not be disturbed by the navigation.
*
* @return {@code true} if gesture event is consumed by one of the children.
*/
public boolean wasLastSideSwipeGestureConsumed() {
return false;
}
/**
* Cancel navigation UI with animation effect.
*/
public void release() {
if (mNavigationHandler != null) mNavigationHandler.release(false);
}
}