/* * Integration of ScrollView and HorizontalScrollView * * For some unknown reason, the H/V scroll bar are missing. */ package com.yfmandroid.widget; import java.util.List; import android.content.Context; import android.graphics.Rect; import android.util.AttributeSet; import android.view.FocusFinder; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; import android.widget.Scroller; /** * Reference to ScrollView and HorizontalScrollView */ public class CustomScrollView extends FrameLayout { static final int ANIMATED_SCROLL_GAP = 250; static final float MAX_SCROLL_FACTOR = 0.5f; private long mLastScroll; private final Rect mTempRect = new Rect(); private Scroller mScroller; /** * Flag to indicate that we are moving focus ourselves. This is so the * code that watches for focus changes initiated outside this ScrollView * knows that it does not have to do anything. */ private boolean mScrollViewMovedFocus; /** * Position of the last motion event. */ private float mLastMotionY; private float 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. */ private boolean mFillViewport; /** * Whether arrow scrolling is animated. */ private boolean mSmoothScrollingEnabled = true; private int mTouchSlop; private int mMinimumVelocity; private int mMaximumVelocity; /** * 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 boolean mFlingEnabled = true; public CustomScrollView(Context context) { this(context, null); } public CustomScrollView(Context context, AttributeSet attrs) { super(context, attrs); initScrollView(); } @Override protected float getTopFadingEdgeStrength() { if (getChildCount() == 0) { return 0.0f; } final int length = getVerticalFadingEdgeLength(); if (getScrollY() < length) { return getScrollY() / (float) length; } return 1.0f; } @Override protected float getLeftFadingEdgeStrength() { if (getChildCount() == 0) { return 0.0f; } final int length = getHorizontalFadingEdgeLength(); if (getScrollX() < length) { return getScrollX() / (float) length; } return 1.0f; } @Override protected float getRightFadingEdgeStrength() { if (getChildCount() == 0) { return 0.0f; } final int length = getHorizontalFadingEdgeLength(); final int rightEdge = getWidth() - getPaddingRight(); final int span = getChildAt(0).getRight() - getScrollX() - rightEdge; if (span < length) { return span / (float) length; } return 1.0f; } @Override protected float getBottomFadingEdgeStrength() { if (getChildCount() == 0) { return 0.0f; } final int length = getVerticalFadingEdgeLength(); final int bottomEdge = getHeight() - getPaddingBottom(); final int span = getChildAt(0).getBottom() - getScrollY() - bottomEdge; 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 getMaxScrollAmountV() { return (int) (MAX_SCROLL_FACTOR * (getBottom() - getTop())); } public int getMaxScrollAmountH() { return (int) (MAX_SCROLL_FACTOR * (getRight() - getLeft())); } private void initScrollView() { mScroller = new Scroller(getContext()); setFocusable(true); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); setWillNotDraw(false); final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); } @Override public void addView(View child) { if (getChildCount() > 0) { throw new IllegalStateException("ScrollView can host only one direct child"); } super.addView(child); } @Override public void addView(View child, int index) { if (getChildCount() > 0) { throw new IllegalStateException("ScrollView 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("ScrollView 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("ScrollView can host only one direct child"); } super.addView(child, index, params); } /** * @return Returns true this ScrollView can be scrolled */ private boolean canScrollV() { View child = getChildAt(0); if (child != null) { int childHeight = child.getHeight(); return getHeight() < childHeight + getPaddingTop() + getPaddingBottom(); } return false; } private boolean canScrollH() { View child = getChildAt(0); if (child != null) { int childWidth = child.getWidth(); return getWidth() < childWidth + getPaddingLeft() + getPaddingRight() ; } return false; } /** * Indicates whether this ScrollView's content is stretched to fill the viewport. * * @return True if the content fills the viewport, false otherwise. */ public boolean isFillViewport() { return mFillViewport; } /** * Indicates this ScrollView whether it should stretch its content height to fill * the viewport or not. * * @param fillViewport True to stretch the content's height to the viewport's * boundaries, false otherwise. */ 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 heightMode = MeasureSpec.getMode(heightMeasureSpec); final int widthMode = MeasureSpec.getMode(widthMeasureSpec); if (heightMode == MeasureSpec.UNSPECIFIED && widthMode == MeasureSpec.UNSPECIFIED) { return; } if (getChildCount() > 0) { final View child = getChildAt(0); int height = getMeasuredHeight(); int width = getMeasuredWidth(); if (child.getMeasuredHeight() < height || child.getMeasuredWidth() < width) { width -= getPaddingLeft(); width -= getPaddingRight(); int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); height -= getPaddingTop(); height -= getPaddingBottom(); int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, 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(); boolean handled = false; if (event.getAction() == KeyEvent.ACTION_DOWN) { switch (event.getKeyCode()) { case KeyEvent.KEYCODE_DPAD_LEFT: if(canScrollH()){ if (!event.isAltPressed()) { handled = arrowScrollH(View.FOCUS_LEFT); } else { handled = fullScrollH(View.FOCUS_LEFT); } } break; case KeyEvent.KEYCODE_DPAD_RIGHT: if(canScrollH()){ if (!event.isAltPressed()) { handled = arrowScrollH(View.FOCUS_RIGHT); } else { handled = fullScrollH(View.FOCUS_RIGHT); } } break; case KeyEvent.KEYCODE_DPAD_UP: if(canScrollV()){ if (!event.isAlt