Commit d51ec213 authored by Jinsuk Kim's avatar Jinsuk Kim Committed by Commit Bot

Android: Tweak gesture navigation UI

- If swiped over the peek threshold, fades to 50% opacity.
- If items to show in the navigation sheet <= 3,
  skip peek state and expand fully.

Bug: 991765, 991794
Change-Id: I64200e7405c51922c20d08c882036627148b5038
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1778102Reviewed-by: default avatarTed Choc <tedchoc@chromium.org>
Commit-Queue: Jinsuk Kim <jinsukkim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#693051}
parent 40ab560f
...@@ -28,6 +28,10 @@ import org.chromium.chrome.browser.util.ColorUtils; ...@@ -28,6 +28,10 @@ import org.chromium.chrome.browser.util.ColorUtils;
public class NavigationBubble extends LinearLayout { public class NavigationBubble extends LinearLayout {
private static final int COLOR_TRANSITION_DURATION_MS = 250; private static final int COLOR_TRANSITION_DURATION_MS = 250;
private static final float FADE_ALPHA = 0.5f;
private static final int FADE_DURATION_MS = 400;
private final ValueAnimator mColorAnimator; private final ValueAnimator mColorAnimator;
private final int mBlue; private final int mBlue;
private final int mBlack; private final int mBlack;
...@@ -55,6 +59,9 @@ public class NavigationBubble extends LinearLayout { ...@@ -55,6 +59,9 @@ public class NavigationBubble extends LinearLayout {
private ImageView mIcon; private ImageView mIcon;
private AnimationListener mListener; private AnimationListener mListener;
// True if arrow bubble is faded out.
private boolean mArrowFaded;
/** /**
* Constructor for inflating from XML. * Constructor for inflating from XML.
*/ */
...@@ -162,4 +169,16 @@ public class NavigationBubble extends LinearLayout { ...@@ -162,4 +169,16 @@ public class NavigationBubble extends LinearLayout {
public TextView getTextView() { public TextView getTextView() {
return mText; return mText;
} }
/**
* Fade out the arrow bubble.
* @param faded {@code true} if the bubble should be faded.
* @param animate {@code true} if animation is needed.
*/
public void setFaded(boolean faded, boolean animate) {
if (faded == mArrowFaded) return;
assert mIcon != null;
animate().alpha(faded ? FADE_ALPHA : 1.f).setDuration(animate ? FADE_DURATION_MS : 0);
mArrowFaded = faded;
}
} }
...@@ -130,7 +130,7 @@ public class NavigationHandler { ...@@ -130,7 +130,7 @@ public class NavigationHandler {
public void onTouchEvent(int action) { public void onTouchEvent(int action) {
if (action == MotionEvent.ACTION_UP) { if (action == MotionEvent.ACTION_UP) {
if (mState == GestureState.DRAGGED && mSideSlideLayout != null) { if (mState == GestureState.DRAGGED && mSideSlideLayout != null) {
mSideSlideLayout.release(!mNavigationSheet.isPeeked()); mSideSlideLayout.release(mNavigationSheet.isHidden());
mNavigationSheet.release(); mNavigationSheet.release();
} else if (mState == GestureState.GLOW && mGlowEffect != null) { } else if (mState == GestureState.GLOW && mGlowEffect != null) {
mGlowEffect.release(); mGlowEffect.release();
...@@ -226,6 +226,12 @@ public class NavigationHandler { ...@@ -226,6 +226,12 @@ public class NavigationHandler {
if (mState == GestureState.DRAGGED && mSideSlideLayout != null) { if (mState == GestureState.DRAGGED && mSideSlideLayout != null) {
mSideSlideLayout.pull(delta); mSideSlideLayout.pull(delta);
mNavigationSheet.onScroll(delta, mSideSlideLayout.getOverscroll()); mNavigationSheet.onScroll(delta, mSideSlideLayout.getOverscroll());
mSideSlideLayout.fadeArrow(!mNavigationSheet.isHidden(), /* animate= */ true);
if (mNavigationSheet.isExpanded()) {
mSideSlideLayout.hideArrow();
mState = GestureState.NONE;
}
} else if (mState == GestureState.GLOW && mGlowEffect != null) { } else if (mState == GestureState.GLOW && mGlowEffect != null) {
mGlowEffect.onScroll(-delta); mGlowEffect.onScroll(-delta);
} }
...@@ -255,7 +261,7 @@ public class NavigationHandler { ...@@ -255,7 +261,7 @@ public class NavigationHandler {
public void release(boolean allowNav) { public void release(boolean allowNav) {
if (mState == GestureState.DRAGGED && mSideSlideLayout != null) { if (mState == GestureState.DRAGGED && mSideSlideLayout != null) {
cancelStopNavigatingRunnable(); cancelStopNavigatingRunnable();
mSideSlideLayout.release(allowNav && !mNavigationSheet.isPeeked()); mSideSlideLayout.release(allowNav && mNavigationSheet.isHidden());
mNavigationSheet.release(); mNavigationSheet.release();
} else if (mState == GestureState.GLOW && mGlowEffect != null) { } else if (mState == GestureState.GLOW && mGlowEffect != null) {
mGlowEffect.release(); mGlowEffect.release();
......
...@@ -53,7 +53,12 @@ interface NavigationSheet { ...@@ -53,7 +53,12 @@ interface NavigationSheet {
public void release() {} public void release() {}
@Override @Override
public boolean isPeeked() { public boolean isHidden() {
return true;
}
@Override
public boolean isExpanded() {
return false; return false;
} }
}; };
...@@ -79,7 +84,12 @@ interface NavigationSheet { ...@@ -79,7 +84,12 @@ interface NavigationSheet {
void release(); void release();
/** /**
* {@code true} if navigation sheet is in peeked state. * @param {@code true} if navigation sheet is in hidden state.
*/
boolean isHidden();
/**
* @param {@code true} if navigation sheet is in expanded (half/full) state.
*/ */
boolean isPeeked(); boolean isExpanded();
} }
...@@ -24,6 +24,7 @@ import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.StateChangeRea ...@@ -24,6 +24,7 @@ import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.StateChangeRea
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController; import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetController;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetObserver; import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetObserver;
import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver; import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver;
import org.chromium.content_public.browser.NavigationHistory;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList; import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.ModelListAdapter; import org.chromium.ui.modelutil.ModelListAdapter;
import org.chromium.ui.modelutil.PropertyKey; import org.chromium.ui.modelutil.PropertyKey;
...@@ -45,6 +46,10 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet ...@@ -45,6 +46,10 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
// Actual amount is capped so it is at most half the screen width. // Actual amount is capped so it is at most half the screen width.
private static final int PEEK_THRESHOLD_DP = 224; private static final int PEEK_THRESHOLD_DP = 224;
// The history item count in the navigation sheet. If the count is equal or smaller,
// the sheet skips peek state and fully expands right away.
private static final int SKIP_PEEK_COUNT = 3;
private final NavigationSheetView mContentView; private final NavigationSheetView mContentView;
private final View mToolbarView; private final View mToolbarView;
private final LayoutInflater mLayoutInflater; private final LayoutInflater mLayoutInflater;
...@@ -59,7 +64,7 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet ...@@ -59,7 +64,7 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
}; };
private final Handler mHandler = new Handler(); private final Handler mHandler = new Handler();
private final Runnable mSheetPeekRunnable; private final Runnable mOpenSheetRunnable;
private final float mPeekSheetThreshold; private final float mPeekSheetThreshold;
private final ModelList mModelList = new ModelList(); private final ModelList mModelList = new ModelList();
...@@ -79,10 +84,6 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet ...@@ -79,10 +84,6 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
} }
} }
// Whether the navigation sheet is visible. Deemed true if it was requested to show even though
// it is not immediately visible. This is to avoid making repeated show/hide requests.
private boolean mSheetVisible;
private boolean mForward; private boolean mForward;
private boolean mShowCloseIndicator; private boolean mShowCloseIndicator;
...@@ -117,16 +118,29 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet ...@@ -117,16 +118,29 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
}, NavigationItemViewBinder::bind); }, NavigationItemViewBinder::bind);
ListView listview = (ListView) mContentView.findViewById(R.id.navigation_entries); ListView listview = (ListView) mContentView.findViewById(R.id.navigation_entries);
listview.setAdapter(mModelAdapter); listview.setAdapter(mModelAdapter);
mOpenSheetRunnable = () -> {
mSheetPeekRunnable = () -> { if (isHidden()) openSheet();
if (!isVisible()) peek(mForward);
}; };
mPeekSheetThreshold = mPeekSheetThreshold =
Math.min(context.getResources().getDisplayMetrics().density * PEEK_THRESHOLD_DP, Math.min(context.getResources().getDisplayMetrics().density * PEEK_THRESHOLD_DP,
parent.getWidth() / 2); parent.getWidth() / 2);
} }
// Transition to either peeked or expanded state.
private void openSheet() {
NavigationHistory history = mDelegate.getHistory(mForward);
mMediator.populateEntries(history);
mBottomSheetController.get().requestShowContent(this, true);
mBottomSheetController.get().getBottomSheet().addObserver(mSheetObserver);
mSheetTriggered = true;
if (history.getEntryCount() <= SKIP_PEEK_COUNT) expandSheet();
}
private void expandSheet() {
mBottomSheetController.get().expandSheet();
GestureNavMetrics.recordHistogram("GestureNavigation.Sheet.Viewed", mForward);
}
// NavigationSheet // NavigationSheet
@Override @Override
...@@ -134,7 +148,6 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet ...@@ -134,7 +148,6 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
if (mBottomSheetController.get() == null) return; if (mBottomSheetController.get() == null) return;
mForward = forward; mForward = forward;
mShowCloseIndicator = showCloseIndicator; mShowCloseIndicator = showCloseIndicator;
setVisible(false);
mSheetTriggered = false; mSheetTriggered = false;
} }
...@@ -144,8 +157,8 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet ...@@ -144,8 +157,8 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
if (mShowCloseIndicator) return; if (mShowCloseIndicator) return;
if (overscroll > mPeekSheetThreshold) { if (overscroll > mPeekSheetThreshold) {
if (isHidden() && Math.abs(delta) > 2.f) { if (isHidden() && Math.abs(delta) > 2.f) {
mHandler.removeCallbacks(mSheetPeekRunnable); mHandler.removeCallbacks(mOpenSheetRunnable);
mHandler.postDelayed(mSheetPeekRunnable, PEEK_HOLD_DELAY_MS); mHandler.postDelayed(mOpenSheetRunnable, PEEK_HOLD_DELAY_MS);
} }
} else if (isPeeked()) { } else if (isPeeked()) {
close(true); close(true);
...@@ -155,27 +168,13 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet ...@@ -155,27 +168,13 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
@Override @Override
public void release() { public void release() {
if (mBottomSheetController.get() == null) return; if (mBottomSheetController.get() == null) return;
mHandler.removeCallbacks(mSheetPeekRunnable); mHandler.removeCallbacks(mOpenSheetRunnable);
// Show navigation sheet if released at peek state.
if (mSheetTriggered) { if (mSheetTriggered) {
GestureNavMetrics.recordHistogram("GestureNavigation.Sheet.Peeked", mForward); GestureNavMetrics.recordHistogram("GestureNavigation.Sheet.Peeked", mForward);
} }
if (isPeeked()) {
mBottomSheetController.get().expandSheet();
GestureNavMetrics.recordHistogram("GestureNavigation.Sheet.Viewed", mForward);
}
}
/** // Show navigation sheet if released at peek state.
* Move the navigation sheet to peek state. if (isPeeked()) expandSheet();
* @param forward {@code true} if the gesture is for navigating forward.
*/
private void peek(boolean forward) {
mMediator.populateEntries(mDelegate.getHistory(forward));
mBottomSheetController.get().getBottomSheet().addObserver(mSheetObserver);
mBottomSheetController.get().requestShowContent(this, true);
setVisible(true);
mSheetTriggered = true;
} }
/** /**
...@@ -184,22 +183,21 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet ...@@ -184,22 +183,21 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
*/ */
private void close(boolean animate) { private void close(boolean animate) {
if (!isHidden()) mBottomSheetController.get().hideContent(this, animate); if (!isHidden()) mBottomSheetController.get().hideContent(this, animate);
setVisible(false);
mBottomSheetController.get().getBottomSheet().removeObserver(mSheetObserver); mBottomSheetController.get().getBottomSheet().removeObserver(mSheetObserver);
mMediator.clear(); mMediator.clear();
} }
/** @Override
* @return {@code true} if the sheet is in hidden state. public boolean isHidden() {
*/ return getTargetOrCurrentState() == SheetState.HIDDEN;
private boolean isHidden() {
return !isVisible() && getTargetOrCurrentState() == SheetState.HIDDEN;
} }
@Override /**
public boolean isPeeked() { * @return {@code true} if the sheet is in peeked state.
*/
private boolean isPeeked() {
if (mBottomSheetController.get() == null) return false; if (mBottomSheetController.get() == null) return false;
return isVisible() && getTargetOrCurrentState() == SheetState.PEEK; return getTargetOrCurrentState() == SheetState.PEEK;
} }
private @SheetState int getTargetOrCurrentState() { private @SheetState int getTargetOrCurrentState() {
...@@ -210,26 +208,11 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet ...@@ -210,26 +208,11 @@ class NavigationSheetCoordinator implements BottomSheetContent, NavigationSheet
return state != SheetState.NONE ? state : sheet.getSheetState(); return state != SheetState.NONE ? state : sheet.getSheetState();
} }
/** @Override
* @return {@code true} if the sheet is in fully expanded state. public boolean isExpanded() {
*/ if (mBottomSheetController.get() == null) return false;
private boolean isExpanded() { int state = getTargetOrCurrentState();
return isVisible() && getTargetOrCurrentState() == SheetState.FULL; return state == SheetState.HALF || state == SheetState.FULL;
}
/**
* Set the visibility flag of the navigation sheet.
* @param visible {@code true} if the sheet becomes visible.
*/
private void setVisible(boolean visible) {
mSheetVisible = visible;
}
/**
* @return {@code true} if the sheet is visible.
*/
private boolean isVisible() {
return mSheetVisible;
} }
// BottomSheetContent // BottomSheetContent
......
...@@ -113,6 +113,7 @@ public class SideSlideLayout extends ViewGroup { ...@@ -113,6 +113,7 @@ public class SideSlideLayout extends ViewGroup {
@Override @Override
public void onAnimationEnd(Animation animation) { public void onAnimationEnd(Animation animation) {
mArrowView.setFaded(false, false);
mArrowView.setVisibility(View.INVISIBLE); mArrowView.setVisibility(View.INVISIBLE);
if (mNavigating) { if (mNavigating) {
if (mListener != null) mListener.onNavigate(mIsForward); if (mListener != null) mListener.onNavigate(mIsForward);
...@@ -266,6 +267,7 @@ public class SideSlideLayout extends ViewGroup { ...@@ -266,6 +267,7 @@ public class SideSlideLayout extends ViewGroup {
mIsBeingDragged = true; mIsBeingDragged = true;
mWillNavigate = false; mWillNavigate = false;
initializeOffset(); initializeOffset();
mArrowView.setFaded(false, false);
return true; return true;
} }
...@@ -318,6 +320,23 @@ public class SideSlideLayout extends ViewGroup { ...@@ -318,6 +320,23 @@ public class SideSlideLayout extends ViewGroup {
setTargetOffsetLeftAndRight(targetX - mCurrentTargetOffset); setTargetOffsetLeftAndRight(targetX - mCurrentTargetOffset);
} }
/**
* Update arrow bubble transparency as navigation sheet state changes.
* @param faded {@code true} if arrow bubble should fade out.
* @param animate {@code true} if animation is needed.
*/
void fadeArrow(boolean faded, boolean animate) {
mArrowView.setFaded(faded, animate);
}
/**
* Hide arrow bubble by making it fade away at the current position.
*/
void hideArrow() {
mNavigating = false;
startHidingAnimation(mNavigateListener);
}
private boolean willNavigate() { private boolean willNavigate() {
return getOverscroll() > mTotalDragDistance * THRESHOLD_MULTIPLIER; return getOverscroll() > mTotalDragDistance * THRESHOLD_MULTIPLIER;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment