diff options
Diffstat (limited to 'core/java/android')
-rw-r--r-- | core/java/android/view/View.java | 92 | ||||
-rw-r--r-- | core/java/android/widget/AbsListView.java | 78 | ||||
-rw-r--r-- | core/java/android/widget/FastScroller.java | 242 |
3 files changed, 375 insertions, 37 deletions
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index da12d46..d27e99d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1967,6 +1967,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility int mUserPaddingBottom; /** + * Cache the paddingLeft set by the user to append to the scrollbar's size. + */ + @ViewDebug.ExportedProperty(category = "padding") + int mUserPaddingLeft; + + /** * @hide */ int mOldWidthMeasureSpec = Integer.MIN_VALUE; @@ -2117,6 +2123,26 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility boolean mCanAcceptDrop; /** + * Position of the vertical scroll bar. + */ + private int mVerticalScrollbarPosition; + + /** + * Position the scroll bar at the default position as determined by the system. + */ + public static final int SCROLLBAR_POSITION_DEFAULT = 0; + + /** + * Position the scroll bar along the left edge. + */ + public static final int SCROLLBAR_POSITION_LEFT = 1; + + /** + * Position the scroll bar along the right edge. + */ + public static final int SCROLLBAR_POSITION_RIGHT = 2; + + /** * Simple constructor to use when creating a view from code. * * @param context The Context the view is running in, through which it can @@ -2448,6 +2474,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility case R.styleable.View_overScrollMode: overScrollMode = a.getInt(attr, OVER_SCROLL_IF_CONTENT_SCROLLS); break; + case R.styleable.View_verticalScrollbarPosition: + mVerticalScrollbarPosition = a.getInt(attr, SCROLLBAR_POSITION_DEFAULT); + break; } } @@ -2708,6 +2737,29 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Set the position of the vertical scroll bar. Should be one of + * {@link #SCROLLBAR_POSITION_DEFAULT}, {@link #SCROLLBAR_POSITION_LEFT} or + * {@link #SCROLLBAR_POSITION_RIGHT}. + * + * @param position Where the vertical scroll bar should be positioned. + */ + public void setVerticalScrollbarPosition(int position) { + if (mVerticalScrollbarPosition != position) { + mVerticalScrollbarPosition = position; + computeOpaqueFlags(); + recomputePadding(); + } + } + + /** + * @return The position where the vertical scroll bar will show, if applicable. + * @see #setVerticalScrollbarPosition(int) + */ + public int getVerticalScrollbarPosition() { + return mVerticalScrollbarPosition; + } + + /** * Register a callback to be invoked when focus of this view changed. * * @param l The callback that will run. @@ -6542,7 +6594,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility (mAlpha >= 1.0f - ViewConfiguration.ALPHA_THRESHOLD); } - private void computeOpaqueFlags() { + /** + * @hide + */ + protected void computeOpaqueFlags() { // Opaque if: // - Has a background // - Background is opaque @@ -6929,8 +6984,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } } - private void recomputePadding() { - setPadding(mPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom); + /** + * @hide + */ + protected void recomputePadding() { + setPadding(mUserPaddingLeft, mPaddingTop, mUserPaddingRight, mUserPaddingBottom); } /** @@ -7213,8 +7271,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility scrollBar.setParameters(computeVerticalScrollRange(), computeVerticalScrollOffset(), computeVerticalScrollExtent(), true); - // TODO: Deal with RTL languages to position scrollbar on left - left = scrollX + width - size - (mUserPaddingRight & inside); + switch (mVerticalScrollbarPosition) { + default: + case SCROLLBAR_POSITION_DEFAULT: + case SCROLLBAR_POSITION_RIGHT: + left = scrollX + width - size - (mUserPaddingRight & inside); + break; + case SCROLLBAR_POSITION_LEFT: + left = scrollX + (mUserPaddingLeft & inside); + break; + } top = scrollY + (mPaddingTop & inside); right = left + size; bottom = scrollY + height - (mUserPaddingBottom & inside); @@ -9003,6 +9069,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility public void setPadding(int left, int top, int right, int bottom) { boolean changed = false; + mUserPaddingLeft = left; mUserPaddingRight = right; mUserPaddingBottom = bottom; @@ -9010,12 +9077,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility // Common case is there are no scroll bars. if ((viewFlags & (SCROLLBARS_VERTICAL|SCROLLBARS_HORIZONTAL)) != 0) { - // TODO: Deal with RTL languages to adjust left padding instead of right. if ((viewFlags & SCROLLBARS_VERTICAL) != 0) { - right += (viewFlags & SCROLLBARS_INSET_MASK) == 0 + // TODO Determine what to do with SCROLLBAR_POSITION_DEFAULT based on RTL settings. + final int offset = (viewFlags & SCROLLBARS_INSET_MASK) == 0 ? 0 : getVerticalScrollbarWidth(); + switch (mVerticalScrollbarPosition) { + case SCROLLBAR_POSITION_DEFAULT: + case SCROLLBAR_POSITION_RIGHT: + right += offset; + break; + case SCROLLBAR_POSITION_LEFT: + left += offset; + break; + } } - if ((viewFlags & SCROLLBARS_HORIZONTAL) == 0) { + if ((viewFlags & SCROLLBARS_HORIZONTAL) != 0) { bottom += (viewFlags & SCROLLBARS_INSET_MASK) == 0 ? 0 : getHorizontalScrollbarHeight(); } diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 135ace6..e0043fa 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -744,6 +744,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } setChoiceMode(a.getInt(R.styleable.AbsListView_choiceMode, CHOICE_MODE_NONE)); + setFastScrollAlwaysVisible( + a.getBoolean(R.styleable.AbsListView_fastScrollAlwaysVisible, false)); a.recycle(); } @@ -1129,6 +1131,49 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } /** + * Set whether or not the fast scroller should always be shown in place of the + * standard scrollbars. Fast scrollers shown in this way will not fade out and will + * be a permanent fixture within the list. Best combined with an inset scroll bar style + * that will ensure enough padding. This will enable fast scrolling if it is not + * already enabled. + * + * @param alwaysShow true if the fast scroller should always be displayed. + * @see #setScrollBarStyle(int) + * @see #setFastScrollEnabled(boolean) + */ + public void setFastScrollAlwaysVisible(boolean alwaysShow) { + if (alwaysShow && !mFastScrollEnabled) { + setFastScrollEnabled(true); + } + + if (mFastScroller != null) { + mFastScroller.setAlwaysShow(alwaysShow); + } + + computeOpaqueFlags(); + recomputePadding(); + } + + /** + * Returns true if the fast scroller is set to always show on this view rather than + * fade out when not in use. + * + * @return true if the fast scroller will always show. + * @see #setFastScrollAlwaysVisible(boolean) + */ + public boolean isFastScrollAlwaysVisible() { + return mFastScrollEnabled && mFastScroller.isAlwaysShowEnabled(); + } + + @Override + public int getVerticalScrollbarWidth() { + if (isEnabled()) { + return Math.max(super.getVerticalScrollbarWidth(), mFastScroller.getWidth()); + } + return super.getVerticalScrollbarWidth(); + } + + /** * Returns the current state of the fast scroll feature. * @see #setFastScrollEnabled(boolean) * @return true if fast scroll is enabled, false otherwise @@ -1138,6 +1183,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return mFastScrollEnabled; } + @Override + public void setVerticalScrollbarPosition(int position) { + super.setVerticalScrollbarPosition(position); + if (mFastScroller != null) { + mFastScroller.setScrollbarPosition(position); + } + } + /** * If fast scroll is visible, then don't draw the vertical scrollbar. * @hide @@ -1969,6 +2022,31 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } @Override + protected boolean isPaddingOffsetRequired() { + return (mGroupFlags & CLIP_TO_PADDING_MASK) != CLIP_TO_PADDING_MASK; + } + + @Override + protected int getLeftPaddingOffset() { + return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingLeft; + } + + @Override + protected int getTopPaddingOffset() { + return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : -mPaddingTop; + } + + @Override + protected int getRightPaddingOffset() { + return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingRight; + } + + @Override + protected int getBottomPaddingOffset() { + return (mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK ? 0 : mPaddingBottom; + } + + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { if (getChildCount() > 0) { mDataChanged = true; diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index aa68a74..f824ff4 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -22,12 +22,14 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; import android.os.Handler; import android.os.SystemClock; -import android.util.TypedValue; import android.view.MotionEvent; +import android.view.View; import android.widget.AbsListView.OnScrollListener; /** @@ -47,9 +49,38 @@ class FastScroller { private static final int STATE_DRAGGING = 3; // Scroll thumb fading out due to inactivity timeout private static final int STATE_EXIT = 4; + + private static final int[] PRESSED_STATES = new int[] { + android.R.attr.state_pressed + }; + + private static final int[] DEFAULT_STATES = new int[0]; + + private static final int[] ATTRS = new int[] { + android.R.attr.textColorPrimary, + com.android.internal.R.attr.fastScrollThumbDrawable, + com.android.internal.R.attr.fastScrollTrackDrawable, + com.android.internal.R.attr.fastScrollPreviewBackgroundLeft, + com.android.internal.R.attr.fastScrollPreviewBackgroundRight, + com.android.internal.R.attr.fastScrollOverlayPosition + }; + + private static final int PRIMARY_TEXT_COLOR = 0; + private static final int THUMB_DRAWABLE = 1; + private static final int TRACK_DRAWABLE = 2; + private static final int PREVIEW_BACKGROUND_LEFT = 3; + private static final int PREVIEW_BACKGROUND_RIGHT = 4; + private static final int OVERLAY_POSITION = 5; + + private static final int OVERLAY_FLOATING = 0; + private static final int OVERLAY_AT_THUMB = 1; private Drawable mThumbDrawable; private Drawable mOverlayDrawable; + private Drawable mTrackDrawable; + + private Drawable mOverlayDrawableLeft; + private Drawable mOverlayDrawableRight; private int mThumbH; private int mThumbW; @@ -80,11 +111,64 @@ class FastScroller { private boolean mChangedBounds; + private int mPosition; + + private boolean mAlwaysShow; + + private int mOverlayPosition; + + private static final int FADE_TIMEOUT = 1500; + + private final Rect mTmpRect = new Rect(); + public FastScroller(Context context, AbsListView listView) { mList = listView; init(context); } + public void setAlwaysShow(boolean alwaysShow) { + mAlwaysShow = alwaysShow; + if (alwaysShow) { + mHandler.removeCallbacks(mScrollFade); + setState(STATE_VISIBLE); + } else if (mState == STATE_VISIBLE) { + mHandler.postDelayed(mScrollFade, FADE_TIMEOUT); + } + } + + public boolean isAlwaysShowEnabled() { + return mAlwaysShow; + } + + private void refreshDrawableState() { + int[] state = mState == STATE_DRAGGING ? PRESSED_STATES : DEFAULT_STATES; + + if (mThumbDrawable != null && mThumbDrawable.isStateful()) { + mThumbDrawable.setState(state); + } + if (mTrackDrawable != null && mTrackDrawable.isStateful()) { + mTrackDrawable.setState(state); + } + } + + public void setScrollbarPosition(int position) { + mPosition = position; + switch (position) { + default: + case View.SCROLLBAR_POSITION_DEFAULT: + case View.SCROLLBAR_POSITION_RIGHT: + mOverlayDrawable = mOverlayDrawableRight; + break; + case View.SCROLLBAR_POSITION_LEFT: + mOverlayDrawable = mOverlayDrawableLeft; + break; + } + } + + public int getWidth() { + return mThumbW; + } + public void setState(int state) { switch (state) { case STATE_NONE: @@ -105,6 +189,7 @@ class FastScroller { break; } mState = state; + refreshDrawableState(); } public int getState() { @@ -114,27 +199,42 @@ class FastScroller { private void resetThumbPos() { final int viewWidth = mList.getWidth(); // Bounds are always top right. Y coordinate get's translated during draw - mThumbDrawable.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH); + switch (mPosition) { + case View.SCROLLBAR_POSITION_DEFAULT: + case View.SCROLLBAR_POSITION_RIGHT: + mThumbDrawable.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH); + break; + case View.SCROLLBAR_POSITION_LEFT: + mThumbDrawable.setBounds(0, 0, mThumbW, mThumbH); + break; + } mThumbDrawable.setAlpha(ScrollFade.ALPHA_MAX); } private void useThumbDrawable(Context context, Drawable drawable) { mThumbDrawable = drawable; - mThumbW = context.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.fastscroll_thumb_width); - mThumbH = context.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.fastscroll_thumb_height); + if (drawable instanceof NinePatchDrawable) { + mThumbW = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.fastscroll_thumb_width); + mThumbH = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.fastscroll_thumb_height); + } else { + mThumbW = drawable.getIntrinsicWidth(); + mThumbH = drawable.getIntrinsicHeight(); + } mChangedBounds = true; } private void init(Context context) { // Get both the scrollbar states drawables final Resources res = context.getResources(); - useThumbDrawable(context, res.getDrawable( - com.android.internal.R.drawable.scrollbar_handle_accelerated_anim2)); + TypedArray ta = context.getTheme().obtainStyledAttributes(ATTRS); + useThumbDrawable(context, ta.getDrawable(ta.getIndex(THUMB_DRAWABLE))); + mTrackDrawable = ta.getDrawable(ta.getIndex(TRACK_DRAWABLE)); - mOverlayDrawable = res.getDrawable( - com.android.internal.R.drawable.menu_submenu_background); + mOverlayDrawableLeft = ta.getDrawable(ta.getIndex(PREVIEW_BACKGROUND_LEFT)); + mOverlayDrawableRight = ta.getDrawable(ta.getIndex(PREVIEW_BACKGROUND_RIGHT)); + mOverlayPosition = ta.getInt(ta.getIndex(OVERLAY_POSITION), OVERLAY_FLOATING); mScrollCompleted = true; @@ -148,9 +248,8 @@ class FastScroller { mPaint.setAntiAlias(true); mPaint.setTextAlign(Paint.Align.CENTER); mPaint.setTextSize(mOverlaySize / 2); - TypedArray ta = context.getTheme().obtainStyledAttributes(new int[] { - android.R.attr.textColorPrimary }); - ColorStateList textColor = ta.getColorStateList(ta.getIndex(0)); + + ColorStateList textColor = ta.getColorStateList(ta.getIndex(PRIMARY_TEXT_COLOR)); int textColorNormal = textColor.getDefaultColor(); mPaint.setColor(textColorNormal); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); @@ -161,6 +260,11 @@ class FastScroller { } mState = STATE_NONE; + refreshDrawableState(); + + ta.recycle(); + + setScrollbarPosition(mList.getVerticalScrollbarPosition()); } void stop() { @@ -188,23 +292,73 @@ class FastScroller { if (alpha < ScrollFade.ALPHA_MAX / 2) { mThumbDrawable.setAlpha(alpha * 2); } - int left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX; - mThumbDrawable.setBounds(left, 0, viewWidth, mThumbH); + int left = 0; + switch (mPosition) { + case View.SCROLLBAR_POSITION_DEFAULT: + case View.SCROLLBAR_POSITION_RIGHT: + left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX; + break; + case View.SCROLLBAR_POSITION_LEFT: + left = -mThumbW + (mThumbW * alpha) / ScrollFade.ALPHA_MAX; + break; + } + mThumbDrawable.setBounds(left, 0, left + mThumbW, mThumbH); mChangedBounds = true; } + if (mTrackDrawable != null) { + final int left = mThumbDrawable.getBounds().left; + final int trackWidth = mTrackDrawable.getIntrinsicWidth(); + final int trackLeft = (left + mThumbW) / 2 - trackWidth / 2; + mTrackDrawable.setBounds(trackLeft, 0, trackLeft + trackWidth, mList.getHeight()); + mTrackDrawable.draw(canvas); + } + canvas.translate(0, y); mThumbDrawable.draw(canvas); canvas.translate(0, -y); // If user is dragging the scroll bar, draw the alphabet overlay if (mState == STATE_DRAGGING && mDrawOverlay) { + if (mOverlayPosition == OVERLAY_AT_THUMB) { + int left = 0; + switch (mPosition) { + default: + case View.SCROLLBAR_POSITION_DEFAULT: + case View.SCROLLBAR_POSITION_RIGHT: + left = Math.max(0, + mThumbDrawable.getBounds().left - mThumbW - mOverlaySize); + break; + case View.SCROLLBAR_POSITION_LEFT: + left = Math.min(mThumbDrawable.getBounds().right + mThumbW, + mList.getWidth() - mOverlaySize); + break; + } + + int top = Math.max(0, + Math.min(y + (mThumbH - mOverlaySize) / 2, mList.getHeight() - mOverlaySize)); + + final RectF pos = mOverlayPos; + pos.left = left; + pos.right = pos.left + mOverlaySize; + pos.top = top; + pos.bottom = pos.top + mOverlaySize; + if (mOverlayDrawable != null) { + mOverlayDrawable.setBounds((int) pos.left, (int) pos.top, + (int) pos.right, (int) pos.bottom); + } + } mOverlayDrawable.draw(canvas); final Paint paint = mPaint; float descent = paint.descent(); final RectF rectF = mOverlayPos; - canvas.drawText(mSectionText, (int) (rectF.left + rectF.right) / 2, - (int) (rectF.bottom + rectF.top) / 2 + mOverlaySize / 4 - descent, paint); + final Rect tmpRect = mTmpRect; + mOverlayDrawable.getPadding(tmpRect); + final int hOff = (tmpRect.right - tmpRect.left) / 2; + final int vOff = (tmpRect.bottom - tmpRect.top) / 2; + canvas.drawText(mSectionText, (int) (rectF.left + rectF.right) / 2 - hOff, + (int) (rectF.bottom + rectF.top) / 2 + mOverlaySize / 4 - descent - vOff, + paint); } else if (mState == STATE_EXIT) { if (alpha == 0) { // Done with exit setState(STATE_NONE); @@ -216,16 +370,27 @@ class FastScroller { void onSizeChanged(int w, int h, int oldw, int oldh) { if (mThumbDrawable != null) { - mThumbDrawable.setBounds(w - mThumbW, 0, w, mThumbH); + switch (mPosition) { + default: + case View.SCROLLBAR_POSITION_DEFAULT: + case View.SCROLLBAR_POSITION_RIGHT: + mThumbDrawable.setBounds(w - mThumbW, 0, w, mThumbH); + break; + case View.SCROLLBAR_POSITION_LEFT: + mThumbDrawable.setBounds(0, 0, mThumbW, mThumbH); + break; + } } - final RectF pos = mOverlayPos; - pos.left = (w - mOverlaySize) / 2; - pos.right = pos.left + mOverlaySize; - pos.top = h / 10; // 10% from top - pos.bottom = pos.top + mOverlaySize; - if (mOverlayDrawable != null) { - mOverlayDrawable.setBounds((int) pos.left, (int) pos.top, - (int) pos.right, (int) pos.bottom); + if (mOverlayPosition == OVERLAY_FLOATING) { + final RectF pos = mOverlayPos; + pos.left = (w - mOverlaySize) / 2; + pos.right = pos.left + mOverlaySize; + pos.top = h / 10; // 10% from top + pos.bottom = pos.top + mOverlaySize; + if (mOverlayDrawable != null) { + mOverlayDrawable.setBounds((int) pos.left, (int) pos.top, + (int) pos.right, (int) pos.bottom); + } } } @@ -257,7 +422,9 @@ class FastScroller { mVisibleItem = firstVisibleItem; if (mState != STATE_DRAGGING) { setState(STATE_VISIBLE); - mHandler.postDelayed(mScrollFade, 1500); + if (!mAlwaysShow) { + mHandler.postDelayed(mScrollFade, FADE_TIMEOUT); + } } } @@ -454,7 +621,11 @@ class FastScroller { setState(STATE_VISIBLE); final Handler handler = mHandler; handler.removeCallbacks(mScrollFade); - handler.postDelayed(mScrollFade, 1000); + if (!mAlwaysShow) { + handler.postDelayed(mScrollFade, 1000); + } + + mList.invalidate(); return true; } } else if (action == MotionEvent.ACTION_MOVE) { @@ -482,7 +653,20 @@ class FastScroller { } boolean isPointInside(float x, float y) { - return x > mList.getWidth() - mThumbW && y >= mThumbY && y <= mThumbY + mThumbH; + boolean inTrack = false; + switch (mPosition) { + default: + case View.SCROLLBAR_POSITION_DEFAULT: + case View.SCROLLBAR_POSITION_RIGHT: + inTrack = x > mList.getWidth() - mThumbW; + break; + case View.SCROLLBAR_POSITION_LEFT: + inTrack = x < mThumbW; + break; + } + + // Allow taps in the track to start moving. + return inTrack && (mTrackDrawable != null || y >= mThumbY && y <= mThumbY + mThumbH); } public class ScrollFade implements Runnable { |