summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt11
-rw-r--r--core/java/android/app/ActionBar.java60
-rw-r--r--core/java/android/view/View.java106
-rw-r--r--core/java/android/view/ViewGroup.java3
-rw-r--r--core/java/android/view/ViewParent.java15
-rw-r--r--core/java/android/view/ViewRootImpl.java2
-rw-r--r--core/java/android/widget/ScrollView.java27
-rw-r--r--core/java/com/android/internal/app/WindowDecorActionBar.java83
-rw-r--r--core/java/com/android/internal/widget/ActionBarOverlayLayout.java282
-rw-r--r--core/res/res/values/attrs.xml3
-rw-r--r--core/res/res/values/public.xml1
11 files changed, 489 insertions, 104 deletions
diff --git a/api/current.txt b/api/current.txt
index 832b2be..6d57796 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -598,6 +598,7 @@ package android {
field public static final int headerBackground = 16843055; // 0x101012f
field public static final int headerDividersEnabled = 16843310; // 0x101022e
field public static final int height = 16843093; // 0x1010155
+ field public static final int hideOnContentScroll = 16843855; // 0x101044f
field public static final int hint = 16843088; // 0x1010150
field public static final int homeAsUpIndicator = 16843531; // 0x101030b
field public static final int homeLayout = 16843549; // 0x101031d
@@ -3064,6 +3065,7 @@ package android.app {
method public abstract android.view.View getCustomView();
method public abstract int getDisplayOptions();
method public abstract int getHeight();
+ method public int getHideOffset();
method public abstract deprecated int getNavigationItemCount();
method public abstract deprecated int getNavigationMode();
method public abstract deprecated int getSelectedNavigationIndex();
@@ -3074,6 +3076,7 @@ package android.app {
method public android.content.Context getThemedContext();
method public abstract java.lang.CharSequence getTitle();
method public abstract void hide();
+ method public boolean isHideOnContentScrollEnabled();
method public abstract boolean isShowing();
method public abstract deprecated android.app.ActionBar.Tab newTab();
method public abstract deprecated void removeAllTabs();
@@ -3092,6 +3095,8 @@ package android.app {
method public abstract void setDisplayShowHomeEnabled(boolean);
method public abstract void setDisplayShowTitleEnabled(boolean);
method public abstract void setDisplayUseLogoEnabled(boolean);
+ method public void setHideOffset(int);
+ method public void setHideOnContentScrollEnabled(boolean);
method public void setHomeActionContentDescription(java.lang.CharSequence);
method public void setHomeActionContentDescription(int);
method public void setHomeAsUpIndicator(android.graphics.drawable.Drawable);
@@ -30185,7 +30190,7 @@ package android.view {
method public boolean dispatchKeyEvent(android.view.KeyEvent);
method public boolean dispatchKeyEventPreIme(android.view.KeyEvent);
method public boolean dispatchKeyShortcutEvent(android.view.KeyEvent);
- method public boolean dispatchNestedFling(float, float);
+ method public boolean dispatchNestedFling(float, float, boolean);
method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
method public boolean dispatchNestedScroll(int, int, int, int, int[]);
method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
@@ -30978,7 +30983,7 @@ package android.view {
method public boolean onInterceptHoverEvent(android.view.MotionEvent);
method public boolean onInterceptTouchEvent(android.view.MotionEvent);
method protected abstract void onLayout(boolean, int, int, int, int);
- method public boolean onNestedFling(android.view.View, float, float);
+ method public boolean onNestedFling(android.view.View, float, float, boolean);
method public void onNestedPreScroll(android.view.View, int, int, int[]);
method public void onNestedScroll(android.view.View, int, int, int, int);
method public void onNestedScrollAccepted(android.view.View, android.view.View, int);
@@ -31116,7 +31121,7 @@ package android.view {
method public abstract boolean isTextAlignmentResolved();
method public abstract boolean isTextDirectionResolved();
method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
- method public abstract boolean onNestedFling(android.view.View, float, float);
+ method public abstract boolean onNestedFling(android.view.View, float, float, boolean);
method public abstract void onNestedPreScroll(android.view.View, int, int, int[]);
method public abstract void onNestedScroll(android.view.View, int, int, int, int);
method public abstract void onNestedScrollAccepted(android.view.View, android.view.View, int);
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 04f62e3..3c3df01 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -932,6 +932,66 @@ public abstract class ActionBar {
*/
public void setHomeActionContentDescription(int resId) { }
+ /**
+ * Enable hiding the action bar on content scroll.
+ *
+ * <p>If enabled, the action bar will scroll out of sight along with a
+ * {@link View#setNestedScrollingEnabled(boolean) nested scrolling child} view's content.
+ * The action bar must be in {@link Window#FEATURE_ACTION_BAR_OVERLAY overlay mode}
+ * to enable hiding on content scroll.</p>
+ *
+ * <p>When partially scrolled off screen the action bar is considered
+ * {@link #hide() hidden}. A call to {@link #show() show} will cause it to return to full view.
+ * </p>
+ * @param hideOnContentScroll true to enable hiding on content scroll.
+ */
+ public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+ if (hideOnContentScroll) {
+ throw new UnsupportedOperationException("Hide on content scroll is not supported in " +
+ "this action bar configuration.");
+ }
+ }
+
+ /**
+ * Return whether the action bar is configured to scroll out of sight along with
+ * a {@link View#setNestedScrollingEnabled(boolean) nested scrolling child}.
+ *
+ * @return true if hide-on-content-scroll is enabled
+ * @see #setHideOnContentScrollEnabled(boolean)
+ */
+ public boolean isHideOnContentScrollEnabled() {
+ return false;
+ }
+
+ /**
+ * Return the current vertical offset of the action bar.
+ *
+ * <p>The action bar's current hide offset is the distance that the action bar is currently
+ * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's
+ * current measured {@link #getHeight() height} (fully invisible).</p>
+ *
+ * @return The action bar's offset toward its fully hidden state in pixels
+ */
+ public int getHideOffset() {
+ return 0;
+ }
+
+ /**
+ * Set the current hide offset of the action bar.
+ *
+ * <p>The action bar's current hide offset is the distance that the action bar is currently
+ * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's
+ * current measured {@link #getHeight() height} (fully invisible).</p>
+ *
+ * @param offset The action bar's offset toward its fully hidden state in pixels.
+ */
+ public void setHideOffset(int offset) {
+ if (offset != 0) {
+ throw new UnsupportedOperationException("Setting an explicit action bar hide offset " +
+ "is not supported in this action bar configuration.");
+ }
+ }
+
/** @hide */
public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 85e3b3d..6afff4d 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -17989,7 +17989,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*
* <p>If this property is set to true the view will be permitted to initiate nested
* scrolling operations with a compatible parent view in the current hierarchy. If this
- * view does not implement nested scrolling this will have no effect.</p>
+ * view does not implement nested scrolling this will have no effect. Disabling nested scrolling
+ * while a nested scroll is in progress has the effect of {@link #stopNestedScroll() stopping}
+ * the nested scroll.</p>
*
* @param enabled true to enable nested scrolling, false to disable
*
@@ -17999,6 +18001,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
if (enabled) {
mPrivateFlags3 |= PFLAG3_NESTED_SCROLLING_ENABLED;
} else {
+ stopNestedScroll();
mPrivateFlags3 &= ~PFLAG3_NESTED_SCROLLING_ENABLED;
}
}
@@ -18138,23 +18141,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
- int startX = 0;
- int startY = 0;
- if (offsetInWindow != null) {
- getLocationInWindow(offsetInWindow);
- startX = offsetInWindow[0];
- startY = offsetInWindow[1];
- }
+ if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
+ int startX = 0;
+ int startY = 0;
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ startX = offsetInWindow[0];
+ startY = offsetInWindow[1];
+ }
- mNestedScrollingParent.onNestedScroll(this, dxConsumed, dyConsumed,
- dxUnconsumed, dyUnconsumed);
+ mNestedScrollingParent.onNestedScroll(this, dxConsumed, dyConsumed,
+ dxUnconsumed, dyUnconsumed);
- if (offsetInWindow != null) {
- getLocationInWindow(offsetInWindow);
- offsetInWindow[0] -= startX;
- offsetInWindow[1] -= startY;
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ offsetInWindow[0] -= startX;
+ offsetInWindow[1] -= startY;
+ }
+ return true;
+ } else if (offsetInWindow != null) {
+ // No motion, no dispatch. Keep offsetInWindow up to date.
+ offsetInWindow[0] = 0;
+ offsetInWindow[1] = 0;
}
- return true;
}
return false;
}
@@ -18180,30 +18189,35 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
*/
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
- int startX = 0;
- int startY = 0;
- if (offsetInWindow != null) {
- getLocationInWindow(offsetInWindow);
- startX = offsetInWindow[0];
- startY = offsetInWindow[1];
- }
-
- if (consumed == null) {
- if (mTempNestedScrollConsumed == null) {
- mTempNestedScrollConsumed = new int[2];
+ if (dx != 0 || dy != 0) {
+ int startX = 0;
+ int startY = 0;
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ startX = offsetInWindow[0];
+ startY = offsetInWindow[1];
}
- consumed = mTempNestedScrollConsumed;
- }
- consumed[0] = 0;
- consumed[1] = 0;
- mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed);
- if (offsetInWindow != null) {
- getLocationInWindow(offsetInWindow);
- offsetInWindow[0] -= startX;
- offsetInWindow[1] -= startY;
+ if (consumed == null) {
+ if (mTempNestedScrollConsumed == null) {
+ mTempNestedScrollConsumed = new int[2];
+ }
+ consumed = mTempNestedScrollConsumed;
+ }
+ consumed[0] = 0;
+ consumed[1] = 0;
+ mNestedScrollingParent.onNestedPreScroll(this, dx, dy, consumed);
+
+ if (offsetInWindow != null) {
+ getLocationInWindow(offsetInWindow);
+ offsetInWindow[0] -= startX;
+ offsetInWindow[1] -= startY;
+ }
+ return consumed[0] != 0 || consumed[1] != 0;
+ } else if (offsetInWindow != null) {
+ offsetInWindow[0] = 0;
+ offsetInWindow[1] = 0;
}
- return consumed[0] != 0 || consumed[1] != 0;
}
return false;
}
@@ -18211,18 +18225,24 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Dispatch a fling to a nested scrolling parent.
*
- * <p>If a nested scrolling child view would normally fling but it is at the edge of its
- * own content it should use this method to delegate the fling to its nested scrolling parent.
- * The view implementation can use a {@link VelocityTracker} to obtain the velocity values
- * to pass.</p>
+ * <p>This method should be used to indicate that a nested scrolling child has detected
+ * suitable conditions for a fling. Generally this means that a touch scroll has ended with a
+ * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+ * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+ * along a scrollable axis.</p>
+ *
+ * <p>If a nested scrolling child view would normally fling but it is at the edge of
+ * its own content, it can use this method to delegate the fling to its nested scrolling
+ * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
*
* @param velocityX Horizontal fling velocity in pixels per second
* @param velocityY Vertical fling velocity in pixels per second
- * @return true if the nested scrolling parent consumed the fling
+ * @param consumed true if the child consumed the fling, false otherwise
+ * @return true if the nested scrolling parent consumed or otherwise reacted to the fling
*/
- public boolean dispatchNestedFling(float velocityX, float velocityY) {
+ public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
- return mNestedScrollingParent.onNestedFling(this, velocityX, velocityY);
+ return mNestedScrollingParent.onNestedFling(this, velocityX, velocityY, consumed);
}
return false;
}
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 8865ab4..43bc0b6 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2342,7 +2342,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
- stopNestedScroll();
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
@@ -5914,7 +5913,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
* @inheritDoc
*/
@Override
- public boolean onNestedFling(View target, float velocityX, float velocityY) {
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return false;
}
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 3cd6449..588b9cd 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -512,14 +512,21 @@ public interface ViewParent {
/**
* Request a fling from a nested scroll.
*
+ * <p>This method signifies that a nested scrolling child has detected suitable conditions
+ * for a fling. Generally this means that a touch scroll has ended with a
+ * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+ * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+ * along a scrollable axis.</p>
+ *
* <p>If a nested scrolling child view would normally fling but it is at the edge of
- * its own content, it can delegate the fling to its nested scrolling parent instead.
- * This method allows the parent to optionally consume the fling.</p>
+ * its own content, it can use this method to delegate the fling to its nested scrolling
+ * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
*
* @param target View that initiated the nested scroll
* @param velocityX Horizontal velocity in pixels per second.
* @param velocityY Vertical velocity in pixels per second
- * @return true if this parent consumed the fling
+ * @param consumed true if the child consumed the fling, false otherwise
+ * @return true if this parent consumed or otherwise reacted to the fling
*/
- public boolean onNestedFling(View target, float velocityX, float velocityY);
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 14e422c..ea52924 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6131,7 +6131,7 @@ public final class ViewRootImpl implements ViewParent,
}
@Override
- public boolean onNestedFling(View target, float velocityX, float velocityY) {
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
return false;
}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 7e8f6b4..3e46f68 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1565,10 +1565,10 @@ public class ScrollView extends FrameLayout {
}
private void flingWithNestedDispatch(int velocityY) {
- if (mScrollY == 0 && velocityY < 0 ||
- mScrollY == getScrollRange() && velocityY > 0) {
- dispatchNestedFling(0, velocityY);
- } else {
+ final boolean canFling = (mScrollY > 0 || velocityY > 0) &&
+ (mScrollY < getScrollRange() || velocityY < 0);
+ dispatchNestedFling(0, velocityY, canFling);
+ if (canFling) {
fling(velocityY);
}
}
@@ -1627,6 +1627,12 @@ public class ScrollView extends FrameLayout {
return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;
}
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int axes) {
+ super.onNestedScrollAccepted(child, target, axes);
+ startNestedScroll(SCROLL_AXIS_VERTICAL);
+ }
+
/**
* @inheritDoc
*/
@@ -1638,16 +1644,23 @@ public class ScrollView extends FrameLayout {
@Override
public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed) {
+ final int oldScrollY = mScrollY;
scrollBy(0, dyUnconsumed);
+ final int myConsumed = mScrollY - oldScrollY;
+ final int myUnconsumed = dyUnconsumed - myConsumed;
+ dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);
}
/**
* @inheritDoc
*/
@Override
- public boolean onNestedFling(View target, float velocityX, float velocityY) {
- flingWithNestedDispatch((int) velocityY);
- return true;
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ if (!consumed) {
+ flingWithNestedDispatch((int) velocityY);
+ return true;
+ }
+ return false;
}
@Override
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index 131f828..66548f0 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -17,7 +17,9 @@
package com.android.internal.app;
import android.animation.ValueAnimator;
+import android.content.res.TypedArray;
import android.view.ViewParent;
+import com.android.internal.R;
import com.android.internal.view.ActionBarPolicy;
import com.android.internal.view.menu.MenuBuilder;
import com.android.internal.view.menu.MenuPopupHelper;
@@ -41,7 +43,6 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
-import android.os.Handler;
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.ContextThemeWrapper;
@@ -57,7 +58,6 @@ import android.widget.SpinnerAdapter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.Map;
/**
* WindowDecorActionBar is the ActionBar implementation used
@@ -66,7 +66,8 @@ import java.util.Map;
* across both the ActionBarView at the top of the screen and
* a horizontal LinearLayout at the bottom which is normally hidden.
*/
-public class WindowDecorActionBar extends ActionBar {
+public class WindowDecorActionBar extends ActionBar implements
+ ActionBarOverlayLayout.ActionBarVisibilityCallback {
private static final String TAG = "WindowDecorActionBar";
private Context mContext;
@@ -116,6 +117,7 @@ public class WindowDecorActionBar extends ActionBar {
private Animator mCurrentShowAnim;
private boolean mShowHideAnimationEnabled;
+ boolean mHideOnContentScroll;
final AnimatorListener mHideListener = new AnimatorListenerAdapter() {
@Override
@@ -132,7 +134,7 @@ public class WindowDecorActionBar extends ActionBar {
mCurrentShowAnim = null;
completeDeferredDestroyActionMode();
if (mOverlayLayout != null) {
- mOverlayLayout.requestFitSystemWindows();
+ mOverlayLayout.requestApplyInsets();
}
}
};
@@ -183,7 +185,7 @@ public class WindowDecorActionBar extends ActionBar {
mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(
com.android.internal.R.id.action_bar_overlay_layout);
if (mOverlayLayout != null) {
- mOverlayLayout.setActionBar(this);
+ mOverlayLayout.setActionBarVisibilityCallback(this);
}
mActionView = (ActionBarView) decor.findViewById(com.android.internal.R.id.action_bar);
mContextView = (ActionBarContextView) decor.findViewById(
@@ -213,6 +215,14 @@ public class WindowDecorActionBar extends ActionBar {
ActionBarPolicy abp = ActionBarPolicy.get(mContext);
setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp);
setHasEmbeddedTabs(abp.hasEmbeddedTabs());
+
+ final TypedArray a = mContext.obtainStyledAttributes(null,
+ com.android.internal.R.styleable.ActionBar,
+ com.android.internal.R.attr.actionBarStyle, 0);
+ if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) {
+ setHideOnContentScrollEnabled(true);
+ }
+ a.recycle();
}
public void onConfigurationChanged(Configuration newConfig) {
@@ -234,17 +244,14 @@ public class WindowDecorActionBar extends ActionBar {
if (isInTabMode) {
mTabScrollView.setVisibility(View.VISIBLE);
if (mOverlayLayout != null) {
- mOverlayLayout.requestFitSystemWindows();
+ mOverlayLayout.requestApplyInsets();
}
} else {
mTabScrollView.setVisibility(View.GONE);
}
}
mActionView.setCollapsable(!mHasEmbeddedTabs && isInTabMode);
- }
-
- public boolean hasNonEmbeddedTabs() {
- return !mHasEmbeddedTabs && getNavigationMode() == NAVIGATION_MODE_TABS;
+ mOverlayLayout.setHasNonEmbeddedTabs(!mHasEmbeddedTabs && isInTabMode);
}
private void ensureTabsExist() {
@@ -279,7 +286,7 @@ public class WindowDecorActionBar extends ActionBar {
}
}
- public void setWindowVisibility(int visibility) {
+ public void onWindowVisibilityChanged(int visibility) {
mCurWindowVisibility = visibility;
}
@@ -453,6 +460,7 @@ public class WindowDecorActionBar extends ActionBar {
mActionMode.finish();
}
+ mOverlayLayout.setHideOnContentScrollEnabled(false);
mContextView.killMode();
ActionModeImpl mode = new ActionModeImpl(callback);
if (mode.dispatchOnCreate()) {
@@ -464,7 +472,7 @@ public class WindowDecorActionBar extends ActionBar {
if (mSplitView.getVisibility() != View.VISIBLE) {
mSplitView.setVisibility(View.VISIBLE);
if (mOverlayLayout != null) {
- mOverlayLayout.requestFitSystemWindows();
+ mOverlayLayout.requestApplyInsets();
}
}
}
@@ -652,6 +660,35 @@ public class WindowDecorActionBar extends ActionBar {
}
}
+ @Override
+ public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+ if (hideOnContentScroll && !mOverlayLayout.isInOverlayMode()) {
+ throw new IllegalStateException("Action bar must be in overlay mode " +
+ "(Window.FEATURE_OVERLAY_ACTION_BAR) to enable hide on content scroll");
+ }
+ mHideOnContentScroll = hideOnContentScroll;
+ mOverlayLayout.setHideOnContentScrollEnabled(hideOnContentScroll);
+ }
+
+ @Override
+ public boolean isHideOnContentScrollEnabled() {
+ return mOverlayLayout.isHideOnContentScrollEnabled();
+ }
+
+ @Override
+ public int getHideOffset() {
+ return mOverlayLayout.getActionBarHideOffset();
+ }
+
+ @Override
+ public void setHideOffset(int offset) {
+ if (offset != 0 && !mOverlayLayout.isInOverlayMode()) {
+ throw new IllegalStateException("Action bar must be in overlay mode " +
+ "(Window.FEATURE_OVERLAY_ACTION_BAR) to set a non-zero hide offset");
+ }
+ mOverlayLayout.setActionBarHideOffset(offset);
+ }
+
private static boolean checkShowingFlags(boolean hiddenByApp, boolean hiddenBySystem,
boolean showingForMode) {
if (showingForMode) {
@@ -737,7 +774,7 @@ public class WindowDecorActionBar extends ActionBar {
mShowListener.onAnimationEnd(null);
}
if (mOverlayLayout != null) {
- mOverlayLayout.requestFitSystemWindows();
+ mOverlayLayout.requestApplyInsets();
}
}
@@ -781,11 +818,7 @@ public class WindowDecorActionBar extends ActionBar {
}
public boolean isShowing() {
- return mNowShowing;
- }
-
- public boolean isSystemShowing() {
- return !mHiddenBySystem;
+ return mNowShowing && getHideOffset() < getHeight();
}
void animateToMode(boolean toActionMode) {
@@ -844,6 +877,18 @@ public class WindowDecorActionBar extends ActionBar {
mActionView.setHomeActionContentDescription(resId);
}
+ @Override
+ public void onContentScrollStarted() {
+ if (mCurrentShowAnim != null) {
+ mCurrentShowAnim.cancel();
+ mCurrentShowAnim = null;
+ }
+ }
+
+ @Override
+ public void onContentScrollStopped() {
+ }
+
/**
* @hide
*/
@@ -894,6 +939,7 @@ public class WindowDecorActionBar extends ActionBar {
// Clear out the context mode views after the animation finishes
mContextView.closeMode();
mActionView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ mOverlayLayout.setHideOnContentScrollEnabled(mHideOnContentScroll);
mActionMode = null;
}
@@ -1204,6 +1250,7 @@ public class WindowDecorActionBar extends ActionBar {
break;
}
mActionView.setCollapsable(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
+ mOverlayLayout.setHasNonEmbeddedTabs(mode == NAVIGATION_MODE_TABS && !mHasEmbeddedTabs);
}
@Override
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index c9dff1a..01bee0c 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -16,18 +16,22 @@
package com.android.internal.widget;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import com.android.internal.app.WindowDecorActionBar;
-
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Canvas;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.util.AttributeSet;
+import android.util.IntProperty;
+import android.util.Property;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
+import android.view.WindowInsets;
+import android.widget.OverScroller;
/**
* Special layout for the containing of an overlay action bar (and its
@@ -38,7 +42,7 @@ public class ActionBarOverlayLayout extends ViewGroup {
private static final String TAG = "ActionBarOverlayLayout";
private int mActionBarHeight;
- private WindowDecorActionBar mActionBar;
+ //private WindowDecorActionBar mActionBar;
private int mWindowVisibility = View.VISIBLE;
// The main UI elements that we handle the layout of.
@@ -54,6 +58,10 @@ public class ActionBarOverlayLayout extends ViewGroup {
private boolean mIgnoreWindowContentOverlay;
private boolean mOverlayMode;
+ private boolean mHasNonEmbeddedTabs;
+ private boolean mHideOnContentScroll;
+ private boolean mAnimatingForFling;
+ private int mHideOnContentScrollReference;
private int mLastSystemUiVisibility;
private final Rect mBaseContentInsets = new Rect();
private final Rect mLastBaseContentInsets = new Rect();
@@ -62,6 +70,84 @@ public class ActionBarOverlayLayout extends ViewGroup {
private final Rect mInnerInsets = new Rect();
private final Rect mLastInnerInsets = new Rect();
+ private ActionBarVisibilityCallback mActionBarVisibilityCallback;
+
+ private final int ACTION_BAR_ANIMATE_DELAY = 600; // ms
+
+ private OverScroller mFlingEstimator;
+
+ private ViewPropertyAnimator mCurrentActionBarTopAnimator;
+ private ViewPropertyAnimator mCurrentActionBarBottomAnimator;
+
+ private final Animator.AnimatorListener mTopAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentActionBarTopAnimator = null;
+ mAnimatingForFling = false;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCurrentActionBarTopAnimator = null;
+ mAnimatingForFling = false;
+ }
+ };
+
+ private final Animator.AnimatorListener mBottomAnimatorListener =
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentActionBarBottomAnimator = null;
+ mAnimatingForFling = false;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCurrentActionBarBottomAnimator = null;
+ mAnimatingForFling = false;
+ }
+ };
+
+ private final Runnable mRemoveActionBarHideOffset = new Runnable() {
+ public void run() {
+ haltActionBarHideOffsetAnimations();
+ mCurrentActionBarTopAnimator = mActionBarTop.animate().translationY(0)
+ .setListener(mTopAnimatorListener);
+ if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
+ mCurrentActionBarBottomAnimator = mActionBarBottom.animate().translationY(0)
+ .setListener(mBottomAnimatorListener);
+ }
+ }
+ };
+
+ private final Runnable mAddActionBarHideOffset = new Runnable() {
+ public void run() {
+ haltActionBarHideOffsetAnimations();
+ mCurrentActionBarTopAnimator = mActionBarTop.animate()
+ .translationY(-mActionBarTop.getHeight())
+ .setListener(mTopAnimatorListener);
+ if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
+ mCurrentActionBarBottomAnimator = mActionBarBottom.animate()
+ .translationY(mActionBarBottom.getHeight())
+ .setListener(mBottomAnimatorListener);
+ }
+ }
+ };
+
+ public static final Property<ActionBarOverlayLayout, Integer> ACTION_BAR_HIDE_OFFSET =
+ new IntProperty<ActionBarOverlayLayout>("actionBarHideOffset") {
+
+ @Override
+ public void setValue(ActionBarOverlayLayout object, int value) {
+ object.setActionBarHideOffset(value);
+ }
+
+ @Override
+ public Integer get(ActionBarOverlayLayout object) {
+ return object.getActionBarHideOffset();
+ }
+ };
+
static final int[] ATTRS = new int [] {
com.android.internal.R.attr.actionBarSize,
com.android.internal.R.attr.windowContentOverlay
@@ -86,14 +172,22 @@ public class ActionBarOverlayLayout extends ViewGroup {
mIgnoreWindowContentOverlay = context.getApplicationInfo().targetSdkVersion <
Build.VERSION_CODES.KITKAT;
+
+ mFlingEstimator = new OverScroller(context);
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ haltActionBarHideOffsetAnimations();
}
- public void setActionBar(WindowDecorActionBar impl) {
- mActionBar = impl;
+ public void setActionBarVisibilityCallback(ActionBarVisibilityCallback cb) {
+ mActionBarVisibilityCallback = cb;
if (getWindowToken() != null) {
// This is being initialized after being added to a window;
// make sure to update all state now.
- mActionBar.setWindowVisibility(mWindowVisibility);
+ mActionBarVisibilityCallback.onWindowVisibilityChanged(mWindowVisibility);
if (mLastSystemUiVisibility != 0) {
int newVis = mLastSystemUiVisibility;
onWindowSystemUiVisibilityChanged(newVis);
@@ -114,6 +208,14 @@ public class ActionBarOverlayLayout extends ViewGroup {
Build.VERSION_CODES.KITKAT;
}
+ public boolean isInOverlayMode() {
+ return mOverlayMode;
+ }
+
+ public void setHasNonEmbeddedTabs(boolean hasNonEmbeddedTabs) {
+ mHasNonEmbeddedTabs = hasNonEmbeddedTabs;
+ }
+
public void setShowingForActionMode(boolean showing) {
if (showing) {
// Here's a fun hack: if the status bar is currently being hidden,
@@ -140,19 +242,18 @@ public class ActionBarOverlayLayout extends ViewGroup {
pullChildren();
final int diff = mLastSystemUiVisibility ^ visible;
mLastSystemUiVisibility = visible;
- final boolean barVisible = (visible&SYSTEM_UI_FLAG_FULLSCREEN) == 0;
- final boolean wasVisible = mActionBar != null ? mActionBar.isSystemShowing() : true;
- final boolean stable = (visible&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
- if (mActionBar != null) {
+ final boolean barVisible = (visible & SYSTEM_UI_FLAG_FULLSCREEN) == 0;
+ final boolean stable = (visible & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0;
+ if (mActionBarVisibilityCallback != null) {
// We want the bar to be visible if it is not being hidden,
// or the app has not turned on a stable UI mode (meaning they
// are performing explicit layout around the action bar).
- mActionBar.enableContentAnimations(!stable);
- if (barVisible || !stable) mActionBar.showForSystem();
- else mActionBar.hideForSystem();
+ mActionBarVisibilityCallback.enableContentAnimations(!stable);
+ if (barVisible || !stable) mActionBarVisibilityCallback.showForSystem();
+ else mActionBarVisibilityCallback.hideForSystem();
}
- if ((diff&SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
- if (mActionBar != null) {
+ if ((diff & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
+ if (mActionBarVisibilityCallback != null) {
requestApplyInsets();
}
}
@@ -162,8 +263,8 @@ public class ActionBarOverlayLayout extends ViewGroup {
protected void onWindowVisibilityChanged(int visibility) {
super.onWindowVisibilityChanged(visibility);
mWindowVisibility = visibility;
- if (mActionBar != null) {
- mActionBar.setWindowVisibility(visibility);
+ if (mActionBarVisibilityCallback != null) {
+ mActionBarVisibilityCallback.onWindowVisibilityChanged(visibility);
}
}
@@ -279,8 +380,8 @@ public class ActionBarOverlayLayout extends ViewGroup {
// This is the standard space needed for the action bar. For stable measurement,
// we can't depend on the size currently reported by it -- this must remain constant.
topInset = mActionBarHeight;
- if (mActionBar != null && mActionBar.hasNonEmbeddedTabs()) {
- View tabs = mActionBarTop.getTabContainer();
+ if (mHasNonEmbeddedTabs) {
+ final View tabs = mActionBarTop.getTabContainer();
if (tabs != null) {
// If tabs are not embedded, increase space on top to account for them.
topInset += mActionBarHeight;
@@ -395,16 +496,138 @@ public class ActionBarOverlayLayout extends ViewGroup {
return false;
}
+ @Override
+ public boolean onStartNestedScroll(View child, View target, int axes) {
+ if ((axes & SCROLL_AXIS_VERTICAL) == 0 || mActionBarTop.getVisibility() != VISIBLE) {
+ return false;
+ }
+ return mHideOnContentScroll;
+ }
+
+ @Override
+ public void onNestedScrollAccepted(View child, View target, int axes) {
+ super.onNestedScrollAccepted(child, target, axes);
+ mHideOnContentScrollReference = getActionBarHideOffset();
+ haltActionBarHideOffsetAnimations();
+ if (mActionBarVisibilityCallback != null) {
+ mActionBarVisibilityCallback.onContentScrollStarted();
+ }
+ }
+
+ @Override
+ public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
+ int dxUnconsumed, int dyUnconsumed) {
+ mHideOnContentScrollReference += dyConsumed;
+ setActionBarHideOffset(mHideOnContentScrollReference);
+ }
+
+ @Override
+ public void onStopNestedScroll(View target) {
+ super.onStopNestedScroll(target);
+ if (mHideOnContentScroll && !mAnimatingForFling) {
+ if (mHideOnContentScrollReference <= mActionBarTop.getHeight()) {
+ postRemoveActionBarHideOffset();
+ } else {
+ postAddActionBarHideOffset();
+ }
+ }
+ if (mActionBarVisibilityCallback != null) {
+ mActionBarVisibilityCallback.onContentScrollStopped();
+ }
+ }
+
+ @Override
+ public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
+ if (!mHideOnContentScroll || !consumed) {
+ return false;
+ }
+ if (shouldHideActionBarOnFling(velocityX, velocityY)) {
+ addActionBarHideOffset();
+ } else {
+ removeActionBarHideOffset();
+ }
+ mAnimatingForFling = true;
+ return true;
+ }
+
void pullChildren() {
if (mContent == null) {
mContent = findViewById(com.android.internal.R.id.content);
- mActionBarTop = (ActionBarContainer)findViewById(
+ mActionBarTop = (ActionBarContainer) findViewById(
com.android.internal.R.id.action_bar_container);
mActionBarView = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
mActionBarBottom = findViewById(com.android.internal.R.id.split_action_bar);
}
}
+ public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+ if (hideOnContentScroll != mHideOnContentScroll) {
+ mHideOnContentScroll = hideOnContentScroll;
+ if (!hideOnContentScroll) {
+ stopNestedScroll();
+ haltActionBarHideOffsetAnimations();
+ setActionBarHideOffset(0);
+ }
+ }
+ }
+
+ public boolean isHideOnContentScrollEnabled() {
+ return mHideOnContentScroll;
+ }
+
+ public int getActionBarHideOffset() {
+ return -((int) mActionBarTop.getTranslationY());
+ }
+
+ public void setActionBarHideOffset(int offset) {
+ haltActionBarHideOffsetAnimations();
+ final int topHeight = mActionBarTop.getHeight();
+ offset = Math.max(0, Math.min(offset, topHeight));
+ mActionBarTop.setTranslationY(-offset);
+ if (mActionBarBottom != null && mActionBarBottom.getVisibility() != GONE) {
+ // Match the hide offset proportionally for a split bar
+ final float fOffset = (float) offset / topHeight;
+ final int bOffset = (int) (mActionBarBottom.getHeight() * fOffset);
+ mActionBarBottom.setTranslationY(bOffset);
+ }
+ }
+
+ private void haltActionBarHideOffsetAnimations() {
+ removeCallbacks(mRemoveActionBarHideOffset);
+ removeCallbacks(mAddActionBarHideOffset);
+ if (mCurrentActionBarTopAnimator != null) {
+ mCurrentActionBarTopAnimator.cancel();
+ }
+ if (mCurrentActionBarBottomAnimator != null) {
+ mCurrentActionBarBottomAnimator.cancel();
+ }
+ }
+
+ private void postRemoveActionBarHideOffset() {
+ haltActionBarHideOffsetAnimations();
+ postDelayed(mRemoveActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
+ }
+
+ private void postAddActionBarHideOffset() {
+ haltActionBarHideOffsetAnimations();
+ postDelayed(mAddActionBarHideOffset, ACTION_BAR_ANIMATE_DELAY);
+ }
+
+ private void removeActionBarHideOffset() {
+ haltActionBarHideOffsetAnimations();
+ mRemoveActionBarHideOffset.run();
+ }
+
+ private void addActionBarHideOffset() {
+ haltActionBarHideOffsetAnimations();
+ mAddActionBarHideOffset.run();
+ }
+
+ private boolean shouldHideActionBarOnFling(float velocityX, float velocityY) {
+ mFlingEstimator.fling(0, 0, 0, (int) velocityY, 0, 0, Integer.MIN_VALUE, Integer.MAX_VALUE);
+ final int finalY = mFlingEstimator.getFinalY();
+ return finalY > mActionBarTop.getHeight();
+ }
public static class LayoutParams extends MarginLayoutParams {
public LayoutParams(Context c, AttributeSet attrs) {
@@ -423,4 +646,13 @@ public class ActionBarOverlayLayout extends ViewGroup {
super(source);
}
}
+
+ public interface ActionBarVisibilityCallback {
+ void onWindowVisibilityChanged(int visibility);
+ void showForSystem();
+ void hideForSystem();
+ void enableContentAnimations(boolean enable);
+ void onContentScrollStarted();
+ void onContentScrollStopped();
+ }
}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index efc1b55..e07ebd4 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -966,7 +966,6 @@
<attr name="colorButtonPressed" format="color" />
<attr name="colorButtonNormalColored" format="color" />
<attr name="colorButtonPressedColored" format="color" />
-
</declare-styleable>
<!-- **************************************************************** -->
@@ -6348,6 +6347,8 @@
<!-- Specifies padding that should be applied to the left and right sides of
system-provided items in the bar. -->
<attr name="itemPadding" format="dimension" />
+ <!-- Set true to hide the action bar on a vertical nested scroll of content. -->
+ <attr name="hideOnContentScroll" format="boolean" />
</declare-styleable>
<declare-styleable name="ActionMode">
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 5ccb05b..d4b3f0d 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2166,6 +2166,7 @@
<public type="attr" name="elevation" />
<public type="attr" name="excludeId" />
<public type="attr" name="excludeClass" />
+ <public type="attr" name="hideOnContentScroll" />
<public-padding type="dimen" name="l_resource_pad" end="0x01050010" />