/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.RectF; import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; import android.util.Log; import android.view.FocusFinder; import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewParent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.AnimationUtils; import java.util.List; /** * Layout container for a view hierarchy that can be scrolled by the user, * allowing it to be larger than the physical display. A HorizontalScrollView * is a {@link FrameLayout}, meaning you should place one child in it * containing the entire contents to scroll; this child may itself be a layout * manager with a complex hierarchy of objects. A child that is often used * is a {@link LinearLayout} in a horizontal orientation, presenting a horizontal * array of top-level items that the user can scroll through. * *
The {@link TextView} class also * takes care of its own scrolling, so does not require a HorizontalScrollView, but * using the two together is possible to achieve the effect of a text view * within a larger container. * *
HorizontalScrollView only supports horizontal scrolling. For vertical scrolling,
* use either {@link ScrollView} or {@link ListView}.
*
* @attr ref android.R.styleable#HorizontalScrollView_fillViewport
*/
public class HorizontalScrollView extends FrameLayout {
private static final int ANIMATED_SCROLL_GAP = ScrollView.ANIMATED_SCROLL_GAP;
private static final float MAX_SCROLL_FACTOR = ScrollView.MAX_SCROLL_FACTOR;
private static final String TAG = "HorizontalScrollView";
private long mLastScroll;
private final Rect mTempRect = new Rect();
private OverScroller mScroller;
private EdgeEffect mEdgeGlowLeft;
private EdgeEffect mEdgeGlowRight;
/**
* Position of the last motion event.
*/
private int mLastMotionX;
/**
* True when the layout has changed but the traversal has not come through yet.
* Ideally the view hierarchy would keep track of this for us.
*/
private boolean mIsLayoutDirty = true;
/**
* The child to give focus to in the event that a child has requested focus while the
* layout is dirty. This prevents the scroll from being wrong if the child has not been
* laid out before requesting focus.
*/
private View mChildToScrollTo = null;
/**
* True if the user is currently dragging this ScrollView around. This is
* not the same as 'is being flinged', which can be checked by
* mScroller.isFinished() (flinging begins when the user lifts his finger).
*/
private boolean mIsBeingDragged = false;
/**
* Determines speed during touch scrolling
*/
private VelocityTracker mVelocityTracker;
/**
* When set to true, the scroll view measure its child to make it fill the currently
* visible area.
*/
@ViewDebug.ExportedProperty(category = "layout")
private boolean mFillViewport;
/**
* Whether arrow scrolling is animated.
*/
private boolean mSmoothScrollingEnabled = true;
private int mTouchSlop;
private int mMinimumVelocity;
private int mMaximumVelocity;
private int mOverscrollDistance;
private int mOverflingDistance;
/**
* ID of the active pointer. This is used to retain consistency during
* drags/flings if multiple pointers are used.
*/
private int mActivePointerId = INVALID_POINTER;
/**
* Sentinel value for no current active pointer.
* Used by {@link #mActivePointerId}.
*/
private static final int INVALID_POINTER = -1;
private SavedState mSavedState;
public HorizontalScrollView(Context context) {
this(context, null);
}
public HorizontalScrollView(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.horizontalScrollViewStyle);
}
public HorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public HorizontalScrollView(
Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initScrollView();
final TypedArray a = context.obtainStyledAttributes(
attrs, android.R.styleable.HorizontalScrollView, defStyleAttr, defStyleRes);
setFillViewport(a.getBoolean(android.R.styleable.HorizontalScrollView_fillViewport, false));
a.recycle();
}
@Override
protected float getLeftFadingEdgeStrength() {
if (getChildCount() == 0) {
return 0.0f;
}
final int length = getHorizontalFadingEdgeLength();
if (mScrollX < length) {
return mScrollX / (float) length;
}
return 1.0f;
}
@Override
protected float getRightFadingEdgeStrength() {
if (getChildCount() == 0) {
return 0.0f;
}
final int length = getHorizontalFadingEdgeLength();
final int rightEdge = getWidth() - mPaddingRight;
final int span = getChildAt(0).getRight() - mScrollX - rightEdge;
if (span < length) {
return span / (float) length;
}
return 1.0f;
}
/**
* @return The maximum amount this scroll view will scroll in response to
* an arrow event.
*/
public int getMaxScrollAmount() {
return (int) (MAX_SCROLL_FACTOR * (mRight - mLeft));
}
private void initScrollView() {
mScroller = new OverScroller(getContext());
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mOverscrollDistance = configuration.getScaledOverscrollDistance();
mOverflingDistance = configuration.getScaledOverflingDistance();
}
@Override
public void addView(View child) {
if (getChildCount() > 0) {
throw new IllegalStateException("HorizontalScrollView can host only one direct child");
}
super.addView(child);
}
@Override
public void addView(View child, int index) {
if (getChildCount() > 0) {
throw new IllegalStateException("HorizontalScrollView can host only one direct child");
}
super.addView(child, index);
}
@Override
public void addView(View child, ViewGroup.LayoutParams params) {
if (getChildCount() > 0) {
throw new IllegalStateException("HorizontalScrollView can host only one direct child");
}
super.addView(child, params);
}
@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
if (getChildCount() > 0) {
throw new IllegalStateException("HorizontalScrollView can host only one direct child");
}
super.addView(child, index, params);
}
/**
* @return Returns true this HorizontalScrollView can be scrolled
*/
private boolean canScroll() {
View child = getChildAt(0);
if (child != null) {
int childWidth = child.getWidth();
return getWidth() < childWidth + mPaddingLeft + mPaddingRight ;
}
return false;
}
/**
* Indicates whether this HorizontalScrollView's content is stretched to
* fill the viewport.
*
* @return True if the content fills the viewport, false otherwise.
*
* @attr ref android.R.styleable#HorizontalScrollView_fillViewport
*/
public boolean isFillViewport() {
return mFillViewport;
}
/**
* Indicates this HorizontalScrollView whether it should stretch its content width
* to fill the viewport or not.
*
* @param fillViewport True to stretch the content's width to the viewport's
* boundaries, false otherwise.
*
* @attr ref android.R.styleable#HorizontalScrollView_fillViewport
*/
public void setFillViewport(boolean fillViewport) {
if (fillViewport != mFillViewport) {
mFillViewport = fillViewport;
requestLayout();
}
}
/**
* @return Whether arrow scrolling will animate its transition.
*/
public boolean isSmoothScrollingEnabled() {
return mSmoothScrollingEnabled;
}
/**
* Set whether arrow scrolling will animate its transition.
* @param smoothScrollingEnabled whether arrow scrolling will animate its transition
*/
public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
mSmoothScrollingEnabled = smoothScrollingEnabled;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!mFillViewport) {
return;
}
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
if (widthMode == MeasureSpec.UNSPECIFIED) {
return;
}
if (getChildCount() > 0) {
final View child = getChildAt(0);
int width = getMeasuredWidth();
if (child.getMeasuredWidth() < width) {
final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, mPaddingTop
+ mPaddingBottom, lp.height);
width -= mPaddingLeft;
width -= mPaddingRight;
int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
// Let the focused view and/or our descendants get the key first
return super.dispatchKeyEvent(event) || executeKeyEvent(event);
}
/**
* You can call this function yourself to have the scroll view perform
* scrolling from a key event, just as if the event had been dispatched to
* it by the view hierarchy.
*
* @param event The key event to execute.
* @return Return true if the event was handled, else false.
*/
public boolean executeKeyEvent(KeyEvent event) {
mTempRect.setEmpty();
if (!canScroll()) {
if (isFocused()) {
View currentFocused = findFocus();
if (currentFocused == this) currentFocused = null;
View nextFocused = FocusFinder.getInstance().findNextFocus(this,
currentFocused, View.FOCUS_RIGHT);
return nextFocused != null && nextFocused != this &&
nextFocused.requestFocus(View.FOCUS_RIGHT);
}
return false;
}
boolean handled = false;
if (event.getAction() == KeyEvent.ACTION_DOWN) {
switch (event.getKeyCode()) {
case KeyEvent.KEYCODE_DPAD_LEFT:
if (!event.isAltPressed()) {
handled = arrowScroll(View.FOCUS_LEFT);
} else {
handled = fullScroll(View.FOCUS_LEFT);
}
break;
case KeyEvent.KEYCODE_DPAD_RIGHT:
if (!event.isAltPressed()) {
handled = arrowScroll(View.FOCUS_RIGHT);
} else {
handled = fullScroll(View.FOCUS_RIGHT);
}
break;
case KeyEvent.KEYCODE_SPACE:
pageScroll(event.isShiftPressed() ? View.FOCUS_LEFT : View.FOCUS_RIGHT);
break;
}
}
return handled;
}
private boolean inChild(int x, int y) {
if (getChildCount() > 0) {
final int scrollX = mScrollX;
final View child = getChildAt(0);
return !(y < child.getTop()
|| y >= child.getBottom()
|| x < child.getLeft() - scrollX
|| x >= child.getRight() - scrollX);
}
return false;
}
private void initOrResetVelocityTracker() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
} else {
mVelocityTracker.clear();
}
}
private void initVelocityTrackerIfNotExists() {
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
}
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept) {
recycleVelocityTracker();
}
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
* This method JUST determines whether we want to intercept the motion.
* If we return true, onMotionEvent will be called and we do the actual
* scrolling there.
*/
/*
* Shortcut the most recurring case: the user is in the dragging
* state and he is moving his finger. We want to intercept this
* motion.
*/
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
return true;
}
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE: {
/*
* mIsBeingDragged == false, otherwise the shortcut would have caught it. Check
* whether the user has moved far enough from his original down touch.
*/
/*
* Locally do absolute value. mLastMotionX is set to the x value
* of the down event.
*/
final int activePointerId = mActivePointerId;
if (activePointerId == INVALID_POINTER) {
// If we don't have a valid id, the touch down wasn't on content.
break;
}
final int pointerIndex = ev.findPointerIndex(activePointerId);
if (pointerIndex == -1) {
Log.e(TAG, "Invalid pointerId=" + activePointerId
+ " in onInterceptTouchEvent");
break;
}
final int x = (int) ev.getX(pointerIndex);
final int xDiff = (int) Math.abs(x - mLastMotionX);
if (xDiff > mTouchSlop) {
mIsBeingDragged = true;
mLastMotionX = x;
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
if (mParent != null) mParent.requestDisallowInterceptTouchEvent(true);
}
break;
}
case MotionEvent.ACTION_DOWN: {
final int x = (int) ev.getX();
if (!inChild((int) x, (int) ev.getY())) {
mIsBeingDragged = false;
recycleVelocityTracker();
break;
}
/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionX = x;
mActivePointerId = ev.getPointerId(0);
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
/*
* If being flinged and user touches the screen, initiate drag;
* otherwise don't. mScroller.isFinished should be false when
* being flinged.
*/
mIsBeingDragged = !mScroller.isFinished();
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
/* Release the drag */
mIsBeingDragged = false;
mActivePointerId = INVALID_POINTER;
if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
postInvalidateOnAnimation();
}
break;
case MotionEvent.ACTION_POINTER_DOWN: {
final int index = ev.getActionIndex();
mLastMotionX = (int) ev.getX(index);
mActivePointerId = ev.getPointerId(index);
break;
}
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
mLastMotionX = (int) ev.getX(ev.findPointerIndex(mActivePointerId));
break;
}
/*
* The only time we want to intercept motion events is if we are in the
* drag mode.
*/
return mIsBeingDragged;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
if (getChildCount() == 0) {
return false;
}
if ((mIsBeingDragged = !mScroller.isFinished())) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
// Remember where the motion event started
mLastMotionX = (int) ev.getX();
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE:
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (activePointerIndex == -1) {
Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
break;
}
final int x = (int) ev.getX(activePointerIndex);
int deltaX = mLastMotionX - x;
if (!mIsBeingDragged && Math.abs(deltaX) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
mIsBeingDragged = true;
if (deltaX > 0) {
deltaX -= mTouchSlop;
} else {
deltaX += mTouchSlop;
}
}
if (mIsBeingDragged) {
// Scroll to follow the motion event
mLastMotionX = x;
final int oldX = mScrollX;
final int oldY = mScrollY;
final int range = getScrollRange();
final int overscrollMode = getOverScrollMode();
final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);
// Calling overScrollBy will call onOverScrolled, which
// calls onScrollChanged if applicable.
if (overScrollBy(deltaX, 0, mScrollX, 0, range, 0,
mOverscrollDistance, 0, true)) {
// Break our velocity if we hit a scroll barrier.
mVelocityTracker.clear();
}
if (canOverscroll) {
final int pulledToX = oldX + deltaX;
if (pulledToX < 0) {
mEdgeGlowLeft.onPull((float) deltaX / getWidth(),
1.f - ev.getY(activePointerIndex) / getHeight());
if (!mEdgeGlowRight.isFinished()) {
mEdgeGlowRight.onRelease();
}
} else if (pulledToX > range) {
mEdgeGlowRight.onPull((float) deltaX / getWidth(),
ev.getY(activePointerIndex) / getHeight());
if (!mEdgeGlowLeft.isFinished()) {
mEdgeGlowLeft.onRelease();
}
}
if (mEdgeGlowLeft != null
&& (!mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished())) {
postInvalidateOnAnimation();
}
}
}
break;
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
if (getChildCount() > 0) {
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
fling(-initialVelocity);
} else {
if (mScroller.springBack(mScrollX, mScrollY, 0,
getScrollRange(), 0, 0)) {
postInvalidateOnAnimation();
}
}
}
mActivePointerId = INVALID_POINTER;
mIsBeingDragged = false;
recycleVelocityTracker();
if (mEdgeGlowLeft != null) {
mEdgeGlowLeft.onRelease();
mEdgeGlowRight.onRelease();
}
}
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
postInvalidateOnAnimation();
}
mActivePointerId = INVALID_POINTER;
mIsBeingDragged = false;
recycleVelocityTracker();
if (mEdgeGlowLeft != null) {
mEdgeGlowLeft.onRelease();
mEdgeGlowRight.onRelease();
}
}
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
}
return true;
}
private void onSecondaryPointerUp(MotionEvent ev) {
final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >>
MotionEvent.ACTION_POINTER_INDEX_SHIFT;
final int pointerId = ev.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
// TODO: Make this decision more intelligent.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mLastMotionX = (int) ev.getX(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
}
}
@Override
public boolean onGenericMotionEvent(MotionEvent event) {
if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
switch (event.getAction()) {
case MotionEvent.ACTION_SCROLL: {
if (!mIsBeingDragged) {
final float hscroll;
if ((event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0) {
hscroll = -event.getAxisValue(MotionEvent.AXIS_VSCROLL);
} else {
hscroll = event.getAxisValue(MotionEvent.AXIS_HSCROLL);
}
if (hscroll != 0) {
final int delta = (int) (hscroll * getHorizontalScrollFactor());
final int range = getScrollRange();
int oldScrollX = mScrollX;
int newScrollX = oldScrollX + delta;
if (newScrollX < 0) {
newScrollX = 0;
} else if (newScrollX > range) {
newScrollX = range;
}
if (newScrollX != oldScrollX) {
super.scrollTo(newScrollX, mScrollY);
return true;
}
}
}
}
}
}
return super.onGenericMotionEvent(event);
}
@Override
public boolean shouldDelayChildPressedState() {
return true;
}
@Override
protected void onOverScrolled(int scrollX, int scrollY,
boolean clampedX, boolean clampedY) {
// Treat animating scrolls differently; see #computeScroll() for why.
if (!mScroller.isFinished()) {
final int oldX = mScrollX;
final int oldY = mScrollY;
mScrollX = scrollX;
mScrollY = scrollY;
invalidateParentIfNeeded();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (clampedX) {
mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0);
}
} else {
super.scrollTo(scrollX, scrollY);
}
awakenScrollBars();
}
/**
* @hide
*/
@Override
public void addClickableRectsForAccessibility(List
* Finds the next focusable component that fits in this View's bounds
* (excluding fading edges) pretending that this View's left is located at
* the parameter left.
*
* Finds the next focusable component that fits in the specified bounds.
* Handles scrolling in response to a "page up/down" shortcut press. This
* method will scroll the view by one page left or right and give the focus
* to the leftmost/rightmost component in the new visible area. If no
* component is a good candidate for focus, this scrollview reclaims the
* focus. Handles scrolling in response to a "home/end" shortcut press. This
* method will scroll the view to the left or right and give the focus
* to the leftmost/rightmost component in the new visible area. If no
* component is a good candidate for focus, this scrollview reclaims the
* focus. Scrolls the view to make the area defined by The scroll range of a scroll view is the overall width of all of its
* children. This version also clamps the scrolling to the bounds of our child.
*/
@Override
public void scrollTo(int x, int y) {
// we rely on the fact the View.scrollBy calls scrollTo.
if (getChildCount() > 0) {
View child = getChildAt(0);
x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
if (x != mScrollX || y != mScrollY) {
super.scrollTo(x, y);
}
}
}
@Override
public void setOverScrollMode(int mode) {
if (mode != OVER_SCROLL_NEVER) {
if (mEdgeGlowLeft == null) {
Context context = getContext();
mEdgeGlowLeft = new EdgeEffect(context);
mEdgeGlowRight = new EdgeEffect(context);
}
} else {
mEdgeGlowLeft = null;
mEdgeGlowRight = null;
}
super.setOverScrollMode(mode);
}
@SuppressWarnings({"SuspiciousNameCombination"})
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mEdgeGlowLeft != null) {
final int scrollX = mScrollX;
if (!mEdgeGlowLeft.isFinished()) {
final int restoreCount = canvas.save();
final int height = getHeight() - mPaddingTop - mPaddingBottom;
canvas.rotate(270);
canvas.translate(-height + mPaddingTop, Math.min(0, scrollX));
mEdgeGlowLeft.setSize(height, getWidth());
if (mEdgeGlowLeft.draw(canvas)) {
postInvalidateOnAnimation();
}
canvas.restoreToCount(restoreCount);
}
if (!mEdgeGlowRight.isFinished()) {
final int restoreCount = canvas.save();
final int width = getWidth();
final int height = getHeight() - mPaddingTop - mPaddingBottom;
canvas.rotate(90);
canvas.translate(-mPaddingTop,
-(Math.max(getScrollRange(), scrollX) + width));
mEdgeGlowRight.setSize(height, width);
if (mEdgeGlowRight.draw(canvas)) {
postInvalidateOnAnimation();
}
canvas.restoreToCount(restoreCount);
}
}
}
private static int clamp(int n, int my, int child) {
if (my >= child || n < 0) {
return 0;
}
if ((my + n) > child) {
return child - my;
}
return n;
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
// Some old apps reused IDs in ways they shouldn't have.
// Don't break them, but they don't get scroll state restoration.
super.onRestoreInstanceState(state);
return;
}
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mSavedState = ss;
requestLayout();
}
@Override
protected Parcelable onSaveInstanceState() {
if (mContext.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR2) {
// Some old apps reused IDs in ways they shouldn't have.
// Don't break them, but they don't get scroll state restoration.
return super.onSaveInstanceState();
}
Parcelable superState = super.onSaveInstanceState();
SavedState ss = new SavedState(superState);
ss.scrollPosition = mScrollX;
ss.isLayoutRtl = isLayoutRtl();
return ss;
}
static class SavedState extends BaseSavedState {
public int scrollPosition;
public boolean isLayoutRtl;
SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
scrollPosition = source.readInt();
isLayoutRtl = (source.readInt() == 0) ? true : false;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(scrollPosition);
dest.writeInt(isLayoutRtl ? 1 : 0);
}
@Override
public String toString() {
return "HorizontalScrollView.SavedState{"
+ Integer.toHexString(System.identityHashCode(this))
+ " scrollPosition=" + scrollPosition
+ " isLayoutRtl=" + isLayoutRtl + "}";
}
public static final Parcelable.Creatorleft
and
* right
visible. This method attempts to give the focus
* to a component visible in this area. If no component can be focused in
* the new visible area, the focus is reclaimed by this scrollview.