Index: chrome/android/java/src/org/chromium/chrome/browser/widget/BottomSheet.java |
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/widget/BottomSheet.java b/chrome/android/java/src/org/chromium/chrome/browser/widget/BottomSheet.java |
index 66470b75c4ee773e8df4bd47bdc572fdf8fd43ff..dade115b6bb2bc4ac751da5e833297b1402c37f8 100644 |
--- a/chrome/android/java/src/org/chromium/chrome/browser/widget/BottomSheet.java |
+++ b/chrome/android/java/src/org/chromium/chrome/browser/widget/BottomSheet.java |
@@ -9,15 +9,22 @@ import android.animation.AnimatorListenerAdapter; |
import android.animation.ObjectAnimator; |
import android.content.Context; |
import android.graphics.Region; |
+import android.support.v4.view.ScrollingView; |
import android.util.AttributeSet; |
import android.view.GestureDetector; |
import android.view.MotionEvent; |
import android.view.VelocityTracker; |
import android.view.View; |
+import android.view.ViewGroup; |
import android.view.animation.DecelerateInterpolator; |
import android.view.animation.Interpolator; |
+import android.widget.FrameLayout; |
import android.widget.LinearLayout; |
+import org.chromium.chrome.R; |
+import org.chromium.chrome.browser.NativePage; |
+import org.chromium.chrome.browser.ntp.NewTabPage; |
+import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
import org.chromium.chrome.browser.util.MathUtils; |
/** |
@@ -48,6 +55,9 @@ public class BottomSheet extends LinearLayout { |
/** The minimum y/x ratio that a scroll must have to be considered vertical. */ |
private static final float MIN_VERTICAL_SCROLL_SLOPE = 2.0f; |
+ /** The minimum difference that two floats must have to be considered different. */ |
+ private static final float EPSILON = 0.0001f; |
+ |
/** |
* Information about the different scroll states of the sheet. Order is important for these, |
* they go from smallest to largest. |
@@ -73,12 +83,31 @@ public class BottomSheet extends LinearLayout { |
/** The height of the toolbar. */ |
private float mToolbarHeight; |
- /** The height of the view that contains the bottom sheet. */ |
+ /** The width and height of the view that contains the bottom sheet. */ |
+ private float mContainerWidth; |
private float mContainerHeight; |
/** The current sheet state. If the sheet is moving, this will be the target state. */ |
private int mCurrentState; |
+ /** Used for getting the current tab. */ |
+ private TabModelSelector mTabModelSelector; |
+ |
+ /** A handle to the native page being shown by the sheet. */ |
+ private NativePage mNativePage; |
+ |
+ /** A handle to the toolbar control container. */ |
+ private View mControlContainer; |
+ |
+ /** A handle to the FrameLayout that holds the content of the bottom sheet. */ |
+ private FrameLayout mBottomSheetContent; |
+ |
+ /** A handle to the main scrolling view in the bottom sheet's content. */ |
+ private ScrollingView mScrollingContentView; |
+ |
+ /** This is a cached array for getting the window location of different views. */ |
+ private final int[] mLocationArr; |
+ |
/** |
* This class is responsible for detecting swipe and scroll events on the bottom sheet or |
* ignoring them when appropriate. |
@@ -100,18 +129,27 @@ public class BottomSheet extends LinearLayout { |
return false; |
} |
- // Cancel the settling animation if it is running so it doesn't conflict with where the |
+ // Cancel the settling animation if it is running so it doesn't conflict with where the |
// user wants to move the sheet. |
+ boolean wasSettleAnimatorRunning = mSettleAnimator != null; |
cancelAnimation(); |
mVelocityTracker.addMovement(e2); |
float currentShownRatio = |
mContainerHeight > 0 ? getSheetOffsetFromBottom() / mContainerHeight : 0; |
+ boolean isSheetInMaxPosition = |
+ areFloatsEqual(currentShownRatio, mStateRatios[mStateRatios.length - 1]); |
+ |
+ // Allow the bottom sheet's content to be scrolled up without dragging the sheet down. |
+ if (!isTouchEventInToolbar(e2) && isSheetInMaxPosition && mScrollingContentView != null |
+ && mScrollingContentView.computeVerticalScrollOffset() > 0) { |
+ mIsScrolling = false; |
+ return false; |
+ } |
// If the sheet is in the max position, don't move if the scroll is upward. |
- if (currentShownRatio >= mStateRatios[mStateRatios.length - 1] |
- && distanceY > 0) { |
+ if (isSheetInMaxPosition && distanceY > 0) { |
mIsScrolling = false; |
return false; |
} |
@@ -122,6 +160,12 @@ public class BottomSheet extends LinearLayout { |
return false; |
} |
+ // Send a notification that the sheet is exiting the peeking state into something that |
+ // will show content. |
+ if (!mIsScrolling && mCurrentState == STATE_PEEK && !wasSettleAnimatorRunning) { |
+ onExitPeekState(); |
+ } |
+ |
float newOffset = getSheetOffsetFromBottom() + distanceY; |
setSheetOffsetFromBottom(MathUtils.clamp(newOffset, getMinOffset(), getMaxOffset())); |
@@ -154,6 +198,7 @@ public class BottomSheet extends LinearLayout { |
public BottomSheet(Context context, AttributeSet atts) { |
super(context, atts); |
+ mLocationArr = new int[2]; |
mVelocityTracker = VelocityTracker.obtain(); |
mGestureDetector = new GestureDetector(context, new BottomSheetSwipeDetector()); |
@@ -207,6 +252,13 @@ public class BottomSheet extends LinearLayout { |
} |
/** |
+ * @param tabModelSelector A TabModelSelector for getting the current tab and activity. |
+ */ |
+ public void setTabModelSelector(TabModelSelector tabModelSelector) { |
+ mTabModelSelector = tabModelSelector; |
+ } |
+ |
+ /** |
* Adds layout change listeners to the views that the bottom sheet depends on. Namely the |
* heights of the root view and control container are important as they are used in many of the |
* calculations in this class. |
@@ -214,7 +266,9 @@ public class BottomSheet extends LinearLayout { |
* @param controlContainer The container for the toolbar. |
*/ |
public void init(View root, View controlContainer) { |
- mToolbarHeight = controlContainer.getHeight(); |
+ mControlContainer = controlContainer; |
+ mToolbarHeight = mControlContainer.getHeight(); |
+ mBottomSheetContent = (FrameLayout) findViewById(R.id.bottom_sheet_content); |
mCurrentState = STATE_PEEK; |
// Listen to height changes on the root. |
@@ -227,8 +281,9 @@ public class BottomSheet extends LinearLayout { |
return; |
} |
+ mContainerWidth = right - left; |
mContainerHeight = bottom - top; |
- updateSheetPeekHeight(mToolbarHeight, mContainerHeight); |
+ updateSheetDimensions(mToolbarHeight, mContainerWidth, mContainerHeight); |
cancelAnimation(); |
setSheetState(mCurrentState, false); |
@@ -246,7 +301,7 @@ public class BottomSheet extends LinearLayout { |
} |
mToolbarHeight = bottom - top; |
- updateSheetPeekHeight(mToolbarHeight, mContainerHeight); |
+ updateSheetDimensions(mToolbarHeight, mContainerWidth, mContainerHeight); |
cancelAnimation(); |
setSheetState(mCurrentState, false); |
@@ -255,6 +310,57 @@ public class BottomSheet extends LinearLayout { |
} |
/** |
+ * Determines if a touch event is inside the toolbar. This assumes the toolbar is the full |
+ * width of the screen and that the toolbar is at the top of the bottom sheet. |
+ * @param e The motion event to test. |
+ * @return True if the event occured in the toolbar region. |
+ */ |
+ private boolean isTouchEventInToolbar(MotionEvent e) { |
+ // Is the touch in the toolbar area? |
+ if (mControlContainer != null) { |
+ mControlContainer.getLocationInWindow(mLocationArr); |
+ } else { |
+ mLocationArr[0] = mLocationArr[1] = 0; |
+ } |
+ |
+ return e.getRawY() < mLocationArr[1] + mToolbarHeight; |
+ } |
+ |
+ /** |
+ * Determine if two floats are equal. |
+ * @param f1 The first float to compare. |
+ * @param f2 The second float to compare. |
+ * @return True if the floats are equal. |
+ */ |
+ private boolean areFloatsEqual(float f1, float f2) { |
+ return Math.abs(f1 - f2) < EPSILON; |
+ } |
+ |
+ /** |
+ * A notification that the sheet is exiting the peek state into one that shows content. |
+ */ |
+ private void onExitPeekState() { |
+ if (mNativePage == null) { |
+ showNativePage(new NewTabPage(mTabModelSelector.getCurrentTab().getActivity(), |
+ mTabModelSelector.getCurrentTab(), mTabModelSelector)); |
+ } |
+ } |
+ |
+ /** |
+ * Show a native page in the bottom sheet's content area. |
+ * @param page The NativePage to show. |
+ */ |
+ private void showNativePage(NativePage page) { |
+ if (mNativePage != null) mBottomSheetContent.removeView(mNativePage.getView()); |
+ |
+ mNativePage = page; |
+ mBottomSheetContent.addView(mNativePage.getView()); |
+ mScrollingContentView = findScrollingChild(mNativePage.getView()); |
+ |
+ mNativePage.updateForUrl(""); |
+ } |
+ |
+ /** |
* Creates an unadjusted version of a MotionEvent. |
* @param e The original event. |
* @return The unadjusted version of the event. |
@@ -266,14 +372,45 @@ public class BottomSheet extends LinearLayout { |
} |
/** |
- * Updates the bottom sheet's peeking height. |
+ * Updates the bottom sheet's peeking and content height. |
* @param toolbarHeight The height of the toolbar control container. |
+ * @param containerWidth The width of the bottom sheet's container. |
* @param containerHeight The height of the bottom sheet's container. |
*/ |
- private void updateSheetPeekHeight(float toolbarHeight, float containerHeight) { |
+ private void updateSheetDimensions(float toolbarHeight, float containerWidth, |
+ float containerHeight) { |
if (containerHeight <= 0) return; |
mStateRatios[0] = toolbarHeight / containerHeight; |
+ |
+ // Compute the height that the content section of the bottom sheet should be. |
+ float contentHeight = |
+ (containerHeight * mStateRatios[mStateRatios.length - 1]) - toolbarHeight; |
+ mBottomSheetContent.setLayoutParams( |
+ new LinearLayout.LayoutParams((int) containerWidth, (int) contentHeight)); |
+ } |
+ |
+ /** |
+ * Find the first ScrollingView in a view hierarchy. |
+ * TODO(mdjones): The root of native pages should be a ScrollingView so this logic is not |
+ * necessary. |
+ * @param view The root of the tree or subtree. |
+ * @return The first scrolling view or null. |
+ */ |
+ private ScrollingView findScrollingChild(View view) { |
+ if (view instanceof ScrollingView) { |
+ return (ScrollingView) view; |
+ } |
+ if (view instanceof ViewGroup) { |
+ ViewGroup group = (ViewGroup) view; |
+ for (int i = 0, count = group.getChildCount(); i < count; i++) { |
+ ScrollingView scrollingChild = findScrollingChild(group.getChildAt(i)); |
+ if (scrollingChild != null) { |
+ return scrollingChild; |
+ } |
+ } |
+ } |
+ return null; |
} |
/** |