Fading scrollbars return. But you have to opt in.

This commit is contained in:
Mike Cleron
2009-09-27 19:14:12 -07:00
parent 5d062bc3de
commit f116bf8884
12 changed files with 369 additions and 31 deletions

View File

@ -24,6 +24,7 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Interpolator;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
@ -514,7 +515,8 @@ import java.util.WeakHashMap;
* The framework provides basic support for views that wish to internally
* scroll their content. This includes keeping track of the X and Y scroll
* offset as well as mechanisms for drawing scrollbars. See
* {@link #scrollBy(int, int)}, {@link #scrollTo(int, int)} for more details.
* {@link #scrollBy(int, int)}, {@link #scrollTo(int, int)}, and
* {@link #awakenScrollBars()} for more details.
* </p>
*
* <a name="Tags"></a>
@ -571,6 +573,8 @@ import java.util.WeakHashMap;
* @attr ref android.R.styleable#View_scrollbarSize
* @attr ref android.R.styleable#View_scrollbarStyle
* @attr ref android.R.styleable#View_scrollbars
* @attr ref android.R.styleable#View_scrollbarDefaultDelayBeforeFade
* @attr ref android.R.styleable#View_scrollbarFadeDuration
* @attr ref android.R.styleable#View_scrollbarTrackHorizontal
* @attr ref android.R.styleable#View_scrollbarThumbHorizontal
* @attr ref android.R.styleable#View_scrollbarThumbVertical
@ -2214,12 +2218,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
protected void initializeScrollbars(TypedArray a) {
initScrollCache();
if (mScrollCache.scrollBar == null) {
mScrollCache.scrollBar = new ScrollBarDrawable();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
if (scrollabilityCache.scrollBar == null) {
scrollabilityCache.scrollBar = new ScrollBarDrawable();
}
final boolean fadeScrollbars = a.getBoolean(R.styleable.View_fadeScrollbars, false);
if (!fadeScrollbars) {
scrollabilityCache.state = ScrollabilityCache.ON;
}
scrollabilityCache.fadeScrollBars = fadeScrollbars;
scrollabilityCache.scrollBarFadeDuration = a.getInt(
R.styleable.View_scrollbarFadeDuration, ViewConfiguration
.getScrollBarFadeDuration());
scrollabilityCache.scrollBarDefaultDelayBeforeFade = a.getInt(
R.styleable.View_scrollbarDefaultDelayBeforeFade,
ViewConfiguration.getScrollDefaultDelay());
scrollabilityCache.scrollBarSize = a.getDimensionPixelSize(
com.android.internal.R.styleable.View_scrollbarSize,
ViewConfiguration.get(mContext).getScaledScrollBarSize());
@ -2263,7 +2283,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
*/
private void initScrollCache() {
if (mScrollCache == null) {
mScrollCache = new ScrollabilityCache(ViewConfiguration.get(mContext));
mScrollCache = new ScrollabilityCache(ViewConfiguration.get(mContext), this);
}
}
@ -4671,7 +4691,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
mScrollX = x;
mScrollY = y;
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
invalidate();
if (!awakenScrollBars()) {
invalidate();
}
}
}
@ -4686,6 +4708,120 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
scrollTo(mScrollX + x, mScrollY + y);
}
/**
* <p>Trigger the scrollbars to draw. When invoked this method starts an
* animation to fade the scrollbars out after a default delay. If a subclass
* provides animated scrolling, the start delay should equal the duration
* of the scrolling animation.</p>
*
* <p>The animation starts only if at least one of the scrollbars is
* enabled, as specified by {@link #isHorizontalScrollBarEnabled()} and
* {@link #isVerticalScrollBarEnabled()}. When the animation is started,
* this method returns true, and false otherwise. If the animation is
* started, this method calls {@link #invalidate()}; in that case the
* caller should not call {@link #invalidate()}.</p>
*
* <p>This method should be invoked every time a subclass directly updates
* the scroll parameters. (See {@link #mScrollX} and {@link #mScrollY})</p>
*
* <p>This method is automatically invoked by {@link #scrollBy(int, int)}
* and {@link #scrollTo(int, int)}.</p>
*
* @return true if the animation is played, false otherwise
*
* @see #awakenScrollBars(int)
* @see #mScrollX
* @see #mScrollY
* @see #scrollBy(int, int)
* @see #scrollTo(int, int)
* @see #isHorizontalScrollBarEnabled()
* @see #isVerticalScrollBarEnabled()
* @see #setHorizontalScrollBarEnabled(boolean)
* @see #setVerticalScrollBarEnabled(boolean)
*/
protected boolean awakenScrollBars() {
return mScrollCache != null &&
awakenScrollBars(mScrollCache.scrollBarDefaultDelayBeforeFade);
}
/**
* <p>
* Trigger the scrollbars to draw. When invoked this method starts an
* animation to fade the scrollbars out after a fixed delay. If a subclass
* provides animated scrolling, the start delay should equal the duration of
* the scrolling animation.
* </p>
*
* <p>
* The animation starts only if at least one of the scrollbars is enabled,
* as specified by {@link #isHorizontalScrollBarEnabled()} and
* {@link #isVerticalScrollBarEnabled()}. When the animation is started,
* this method returns true, and false otherwise. If the animation is
* started, this method calls {@link #invalidate()}; in that case the caller
* should not call {@link #invalidate()}.
* </p>
*
* <p>
* This method should be invoked everytime a subclass directly updates the
* scroll parameters. (See {@link #mScrollX} and {@link #mScrollY})
* </p>
*
* @param startDelay the delay, in milliseconds, after which the animation
* should start; when the delay is 0, the animation starts
* immediately
* @return true if the animation is played, false otherwise
*
* @see #mScrollX
* @see #mScrollY
* @see #scrollBy(int, int)
* @see #scrollTo(int, int)
* @see #isHorizontalScrollBarEnabled()
* @see #isVerticalScrollBarEnabled()
* @see #setHorizontalScrollBarEnabled(boolean)
* @see #setVerticalScrollBarEnabled(boolean)
*/
protected boolean awakenScrollBars(int startDelay) {
final ScrollabilityCache scrollCache = mScrollCache;
if (scrollCache == null || !scrollCache.fadeScrollBars) {
return false;
}
if (scrollCache.scrollBar == null) {
scrollCache.scrollBar = new ScrollBarDrawable();
}
if (isHorizontalScrollBarEnabled() || isVerticalScrollBarEnabled()) {
// Invalidate to show the scrollbars
invalidate();
if (scrollCache.state == ScrollabilityCache.OFF) {
// FIXME: this is copied from WindowManagerService.
// We should get this value from the system when it
// is possible to do so.
final int KEY_REPEAT_FIRST_DELAY = 750;
startDelay = Math.max(KEY_REPEAT_FIRST_DELAY, startDelay);
}
// Tell mScrollCache when we should start fading. This may
// extend the fade start time if one was already scheduled
long fadeStartTime = SystemClock.uptimeMillis() + startDelay;
scrollCache.fadeStartTime = fadeStartTime;
scrollCache.state = ScrollabilityCache.ON;
// Schedule our fader to run, unscheduling any old ones first
if (mAttachInfo != null) {
mAttachInfo.mHandler.removeCallbacks(scrollCache);
mAttachInfo.mHandler.postAtTime(scrollCache, fadeStartTime);
}
return true;
}
return false;
}
/**
* Mark the the area defined by dirty as needing to be drawn. If the view is
* visible, {@link #onDraw} will be called at some point in the future.
@ -5344,11 +5480,49 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* scrollbars are painted only if they have been awakened first.</p>
*
* @param canvas the canvas on which to draw the scrollbars
*
* @see #awakenScrollBars(int)
*/
private void onDrawScrollBars(Canvas canvas) {
// scrollbars are drawn only when the animation is running
final ScrollabilityCache cache = mScrollCache;
if (cache != null) {
int state = cache.state;
if (state == ScrollabilityCache.OFF) {
return;
}
boolean invalidate = false;
if (state == ScrollabilityCache.FADING) {
// We're fading -- get our fade interpolation
if (cache.interpolatorValues == null) {
cache.interpolatorValues = new float[1];
}
float[] values = cache.interpolatorValues;
// Stops the animation if we're done
if (cache.scrollBarInterpolator.timeToValues(values) ==
Interpolator.Result.FREEZE_END) {
cache.state = ScrollabilityCache.OFF;
} else {
cache.scrollBar.setAlpha(Math.round(values[0]));
}
// This will make the scroll bars inval themselves after
// drawing. We only want this when we're fading so that
// we prevent excessive redraws
invalidate = true;
} else {
// We're just on -- but we may have been fading before so
// reset alpha
cache.scrollBar.setAlpha(255);
}
final int viewFlags = mViewFlags;
final boolean drawHorizontalScrollBar =
@ -5371,19 +5545,22 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
final int scrollY = mScrollY;
final int inside = (viewFlags & SCROLLBARS_OUTSIDE_MASK) == 0 ? ~0 : 0;
int left, top, right, bottom;
if (drawHorizontalScrollBar) {
scrollBar.setParameters(
computeHorizontalScrollRange(),
scrollBar.setParameters(computeHorizontalScrollRange(),
computeHorizontalScrollOffset(),
computeHorizontalScrollExtent(), false);
final int top = scrollY + height - size - (mUserPaddingBottom & inside);
final int verticalScrollBarGap = drawVerticalScrollBar ?
getVerticalScrollbarWidth() : 0;
onDrawHorizontalScrollBar(canvas, scrollBar,
scrollX + (mPaddingLeft & inside),
top,
scrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap,
top + size);
getVerticalScrollbarWidth() : 0;
top = scrollY + height - size - (mUserPaddingBottom & inside);
left = scrollX + (mPaddingLeft & inside);
right = scrollX + width - (mUserPaddingRight & inside) - verticalScrollBarGap;
bottom = top + size;
onDrawHorizontalScrollBar(canvas, scrollBar, left, top, right, bottom);
if (invalidate) {
invalidate(left, top, right, bottom);
}
}
if (drawVerticalScrollBar) {
@ -5391,12 +5568,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
computeVerticalScrollOffset(),
computeVerticalScrollExtent(), true);
// TODO: Deal with RTL languages to position scrollbar on left
final int left = scrollX + width - size - (mUserPaddingRight & inside);
onDrawVerticalScrollBar(canvas, scrollBar,
left,
scrollY + (mPaddingTop & inside),
left + size,
scrollY + height - (mUserPaddingBottom & inside));
left = scrollX + width - size - (mUserPaddingRight & inside);
top = scrollY + (mPaddingTop & inside);
right = left + size;
bottom = scrollY + height - (mUserPaddingBottom & inside);
onDrawVerticalScrollBar(canvas, scrollBar, left, top, right, bottom);
if (invalidate) {
invalidate(left, top, right, bottom);
}
}
}
}
@ -5424,7 +5603,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* @see #computeHorizontalScrollExtent()
* @see #computeHorizontalScrollOffset()
* @see android.widget.ScrollBarDrawable
* @hide
* @hide
*/
protected void onDrawHorizontalScrollBar(Canvas canvas,
Drawable scrollBar,
@ -8731,21 +8910,62 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
* is supported. This avoids keeping too many unused fields in most
* instances of View.</p>
*/
private static class ScrollabilityCache {
private static class ScrollabilityCache implements Runnable {
/**
* Scrollbars are not visible
*/
public static final int OFF = 0;
/**
* Scrollbars are visible
*/
public static final int ON = 1;
/**
* Scrollbars are fading away
*/
public static final int FADING = 2;
public boolean fadeScrollBars;
public int fadingEdgeLength;
public int scrollBarDefaultDelayBeforeFade;
public int scrollBarFadeDuration;
public int scrollBarSize;
public ScrollBarDrawable scrollBar;
public float[] interpolatorValues;
public View host;
public final Paint paint;
public final Matrix matrix;
public Shader shader;
public final Interpolator scrollBarInterpolator = new Interpolator(1, 2);
private final float[] mOpaque = {255.0f};
private final float[] mTransparent = {0.0f};
/**
* When fading should start. This time moves into the future every time
* a new scroll happens. Measured based on SystemClock.uptimeMillis()
*/
public long fadeStartTime;
/**
* The current state of the scrollbars: ON, OFF, or FADING
*/
public int state = OFF;
private int mLastColor;
public ScrollabilityCache(ViewConfiguration configuration) {
public ScrollabilityCache(ViewConfiguration configuration, View host) {
fadingEdgeLength = configuration.getScaledFadingEdgeLength();
scrollBarSize = configuration.getScaledScrollBarSize();
scrollBarDefaultDelayBeforeFade = configuration.getScrollDefaultDelay();
scrollBarFadeDuration = configuration.getScrollBarFadeDuration();
paint = new Paint();
matrix = new Matrix();
@ -8755,6 +8975,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
paint.setShader(shader);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
this.host = host;
}
public void setFadeColor(int color) {
@ -8770,5 +8991,32 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility
paint.setXfermode(null);
}
}
public void run() {
long now = SystemClock.uptimeMillis();
if (now >= fadeStartTime) {
// the animation fades the scrollbars out by changing
// the opacity (alpha) from fully opaque to fully
// transparent
int nextFrame = (int) now;
int framesCount = 0;
Interpolator interpolator = scrollBarInterpolator;
// Start opaque
interpolator.setKeyFrame(framesCount++, nextFrame, mOpaque);
// End transparent
nextFrame += scrollBarFadeDuration;
interpolator.setKeyFrame(framesCount, nextFrame, mTransparent);
state = FADING;
// Kick off the fade animation
host.invalidate();
}
}
}
}

View File

@ -30,6 +30,16 @@ public class ViewConfiguration {
*/
private static final int SCROLL_BAR_SIZE = 10;
/**
* Duration of the fade when scrollbars fade away in milliseconds
*/
private static final int SCROLL_BAR_FADE_DURATION = 250;
/**
* Default delay before the scrollbars fade in milliseconds
*/
private static final int SCROLL_BAR_DEFAULT_DELAY = 300;
/**
* Defines the length of the fading edges in pixels
*/
@ -220,6 +230,20 @@ public class ViewConfiguration {
return mScrollbarSize;
}
/**
* @return Duration of the fade when scrollbars fade away in milliseconds
*/
public static int getScrollBarFadeDuration() {
return SCROLL_BAR_FADE_DURATION;
}
/**
* @return Default delay before the scrollbars fade in milliseconds
*/
public static int getScrollDefaultDelay() {
return SCROLL_BAR_DEFAULT_DELAY;
}
/**
* @return the length of the fading edges in pixels
*

View File

@ -2480,6 +2480,7 @@ public class WebView extends AbsoluteLayout
// Log.d(LOGTAG, "startScroll: " + dx + " " + dy);
mScroller.startScroll(mScrollX, mScrollY, dx, dy,
animationDuration > 0 ? animationDuration : computeDuration(dx, dy));
awakenScrollBars(mScroller.getDuration());
invalidate();
} else {
abortAnimation(); // just in case
@ -4326,6 +4327,7 @@ public class WebView extends AbsoluteLayout
// resume the webcore update.
final int time = mScroller.getDuration();
mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_UPDATE, time);
awakenScrollBars(time);
invalidate();
}

View File

@ -2444,7 +2444,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
if (spaceAbove >= absIncrementalDeltaY && spaceBelow >= absIncrementalDeltaY) {
hideSelector();
offsetChildrenTopAndBottom(incrementalDeltaY);
invalidate();
if (!awakenScrollBars()) {
invalidate();
}
mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
} else {
final int firstPosition = mFirstPosition;
@ -2527,6 +2529,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mBlockLayoutRequests = false;
invokeOnItemScrollListener();
awakenScrollBars();
}
}

View File

@ -1340,8 +1340,23 @@ public class GridView extends AbsListView {
*/
@Override
void setSelectionInt(int position) {
int previousSelectedPosition = mNextSelectedPosition;
setNextSelectedPositionInt(position);
layoutChildren();
final int next = mStackFromBottom ? mItemCount - 1 - mNextSelectedPosition :
mNextSelectedPosition;
final int previous = mStackFromBottom ? mItemCount - 1
- previousSelectedPosition : previousSelectedPosition;
final int nextRow = next / mNumColumns;
final int previousRow = previous / mNumColumns;
if (nextRow != previousRow) {
awakenScrollBars();
}
}
@Override
@ -1471,6 +1486,7 @@ public class GridView extends AbsListView {
if (nextPage >= 0) {
setSelectionInt(nextPage);
invokeOnItemScrollListener();
awakenScrollBars();
return true;
}
@ -1497,6 +1513,10 @@ public class GridView extends AbsListView {
invokeOnItemScrollListener();
moved = true;
}
if (moved) {
awakenScrollBars();
}
return moved;
}
@ -1563,6 +1583,10 @@ public class GridView extends AbsListView {
invokeOnItemScrollListener();
}
if (moved) {
awakenScrollBars();
}
return moved;
}

View File

@ -829,6 +829,7 @@ public class HorizontalScrollView extends FrameLayout {
long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
if (duration > ANIMATED_SCROLL_GAP) {
mScroller.startScroll(mScrollX, mScrollY, dx, dy);
awakenScrollBars(mScroller.getDuration());
invalidate();
} else {
if (!mScroller.isFinished()) {
@ -1172,6 +1173,7 @@ public class HorizontalScrollView extends FrameLayout {
mScrollViewMovedFocus = false;
}
awakenScrollBars(mScroller.getDuration());
invalidate();
}
}

View File

@ -1818,13 +1818,29 @@ public class ListView extends AbsListView {
/**
* Makes the item at the supplied position selected.
*
*
* @param position the position of the item to select
*/
@Override
void setSelectionInt(int position) {
setNextSelectedPositionInt(position);
boolean awakeScrollbars = false;
final int selectedPosition = mSelectedPosition;
if (selectedPosition >= 0) {
if (position == selectedPosition - 1) {
awakeScrollbars = true;
} else if (position == selectedPosition + 1) {
awakeScrollbars = true;
}
}
layoutChildren();
if (awakeScrollbars) {
awakenScrollBars();
}
}
/**
@ -2084,7 +2100,9 @@ public class ListView extends AbsListView {
setSelectionInt(position);
invokeOnItemScrollListener();
invalidate();
if (!awakenScrollBars()) {
invalidate();
}
return true;
}
@ -2125,7 +2143,8 @@ public class ListView extends AbsListView {
}
}
if (moved) {
if (moved && !awakenScrollBars()) {
awakenScrollBars();
invalidate();
}
@ -2270,7 +2289,9 @@ public class ListView extends AbsListView {
positionSelector(selectedView);
mSelectedTop = selectedView.getTop();
}
invalidate();
if (!awakenScrollBars()) {
invalidate();
}
invokeOnItemScrollListener();
return true;
}

View File

@ -832,6 +832,7 @@ public class ScrollView extends FrameLayout {
long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
if (duration > ANIMATED_SCROLL_GAP) {
mScroller.startScroll(mScrollX, mScrollY, dx, dy);
awakenScrollBars(mScroller.getDuration());
invalidate();
} else {
if (!mScroller.isFinished()) {
@ -1175,6 +1176,7 @@ public class ScrollView extends FrameLayout {
mScrollViewMovedFocus = false;
}
awakenScrollBars(mScroller.getDuration());
invalidate();
}
}

View File

@ -5572,6 +5572,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (duration > ANIMATED_SCROLL_GAP) {
mScroller.startScroll(mScrollX, mScrollY, dx, dy);
awakenScrollBars(mScroller.getDuration());
invalidate();
} else {
if (!mScroller.isFinished()) {

View File

@ -1162,6 +1162,12 @@
set, else it will be false. -->
<attr name="isScrollContainer" format="boolean" />
<!-- Defines whether to fade out scrollbars when they are not in use. -->
<attr name="fadeScrollbars" format="boolean" />
<!-- Defines the delay in milliseconds that a scrollbar takes to fade out. -->
<attr name="scrollbarFadeDuration" format="integer" />
<!-- Defines the delay in milliseconds that a scrollbar waits before fade out. -->
<attr name="scrollbarDefaultDelayBeforeFade" format="integer" />
<!-- Sets the width of vertical scrollbars and height of horizontal scrollbars. -->
<attr name="scrollbarSize" format="dimension" />
<!-- Defines the horizontal scrollbar thumb drawable. -->

View File

@ -1172,7 +1172,10 @@
<public type="attr" name="thumbnail" />
<public type="attr" name="detachWallpaper" />
<public type="attr" name="finishOnCloseSystemDialogs" />
<public type="attr" name="scrollbarFadeDuration" />
<public type="attr" name="scrollbarDefaultDelayBeforeFade" />
<public type="attr" name="fadeScrollbars" />
<public type="style" name="Theme.Wallpaper" />
<public type="style" name="Theme.Wallpaper.NoTitleBar" />
<public type="style" name="Theme.Wallpaper.NoTitleBar.Fullscreen" />

View File

@ -126,6 +126,8 @@
<item name="panelTextAppearance">?android:attr/textAppearanceInverse</item>
<!-- Scrollbar attributes -->
<item name="scrollbarFadeDuration">250</item>
<item name="scrollbarDefaultDelayBeforeFade">300</item>
<item name="scrollbarSize">10dip</item>
<item name="scrollbarThumbHorizontal">@android:drawable/scrollbar_handle_horizontal</item>
<item name="scrollbarThumbVertical">@android:drawable/scrollbar_handle_vertical</item>