|
Android example source code file (HorizontalScrollView.java)
The HorizontalScrollView.java Android example source code/*
* 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.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 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.
*
* <p>You should never use a HorizontalScrollView with a {@link ListView}, since
* ListView takes care of its own scrolling. Most importantly, doing this
* defeats all of the important optimizations in ListView for dealing with
* large lists, since it effectively forces the ListView to display its entire
* list of items to fill up the infinite container supplied by HorizontalScrollView.
*
* <p>The {@link TextView} class also
* takes care of its own scrolling, so does not require a ScrollView, but
* using the two together is possible to achieve the effect of a text view
* within a larger container.
*
* <p>HorizontalScrollView only supports horizontal scrolling.
*/
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 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 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;
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 defStyle) {
super(context, attrs, defStyle);
initScrollView();
TypedArray a = context.obtainStyledAttributes(attrs,
android.R.styleable.HorizontalScrollView, defStyle, 0);
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 Scroller(getContext());
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
final ViewConfiguration configuration = ViewConfiguration.get(mContext);
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}
@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 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 width to fill
* the viewport or not.
*
* @param fillViewport True to stretch the content's width 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 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;
}
@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);
final float x = ev.getX(pointerIndex);
final int xDiff = (int) Math.abs(x - mLastMotionX);
if (xDiff > mTouchSlop) {
mIsBeingDragged = true;
mLastMotionX = x;
if (mParent != null) mParent.requestDisallowInterceptTouchEvent(true);
}
break;
}
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
if (!inChild((int) x, (int) ev.getY())) {
mIsBeingDragged = false;
break;
}
/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionX = x;
mActivePointerId = ev.getPointerId(0);
/*
* 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;
break;
case MotionEvent.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
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) {
if (ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
// Don't handle edge touches immediately -- they may actually belong to one of our
// descendants.
return false;
}
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
if (!(mIsBeingDragged = inChild((int) x, (int) ev.getY()))) {
return false;
}
/*
* 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 = x;
mActivePointerId = ev.getPointerId(0);
break;
}
case MotionEvent.ACTION_MOVE:
if (mIsBeingDragged) {
// Scroll to follow the motion event
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(activePointerIndex);
final int deltaX = (int) (mLastMotionX - x);
mLastMotionX = x;
scrollBy(deltaX, 0);
}
break;
case MotionEvent.ACTION_UP:
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) {
fling(-initialVelocity);
}
mActivePointerId = INVALID_POINTER;
mIsBeingDragged = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
break;
case MotionEvent.ACTION_CANCEL:
if (mIsBeingDragged && getChildCount() > 0) {
mActivePointerId = INVALID_POINTER;
mIsBeingDragged = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
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 = ev.getX(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
}
}
private int getScrollRange() {
int scrollRange = 0;
if (getChildCount() > 0) {
View child = getChildAt(0);
scrollRange = Math.max(0,
child.getWidth() - getWidth() - mPaddingLeft - mPaddingRight);
}
return scrollRange;
}
/**
* <p>
* 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.
* </p>
*
* @param leftFocus look for a candidate is the one at the left of the bounds
* if leftFocus is true, or at the right of the bounds if leftFocus
* is false
* @param left the left offset of the bounds in which a focusable must be
* found (the fading edge is assumed to start at this position)
* @param preferredFocusable the View that has highest priority and will be
* returned if it is within my bounds (null is valid)
* @return the next focusable component in the bounds or null if none can be found
*/
private View findFocusableViewInMyBounds(final boolean leftFocus,
final int left, View preferredFocusable) {
/*
* The fading edge's transparent side should be considered for focus
* since it's mostly visible, so we divide the actual fading edge length
* by 2.
*/
final int fadingEdgeLength = getHorizontalFadingEdgeLength() / 2;
final int leftWithoutFadingEdge = left + fadingEdgeLength;
final int rightWithoutFadingEdge = left + getWidth() - fadingEdgeLength;
if ((preferredFocusable != null)
&& (preferredFocusable.getLeft() < rightWithoutFadingEdge)
&& (preferredFocusable.getRight() > leftWithoutFadingEdge)) {
return preferredFocusable;
}
return findFocusableViewInBounds(leftFocus, leftWithoutFadingEdge,
rightWithoutFadingEdge);
}
/**
* <p>
* Finds the next focusable component that fits in the specified bounds.
* </p>
*
* @param leftFocus look for a candidate is the one at the left of the bounds
* if leftFocus is true, or at the right of the bounds if
* leftFocus is false
* @param left the left offset of the bounds in which a focusable must be
* found
* @param right the right offset of the bounds in which a focusable must
* be found
* @return the next focusable component in the bounds or null if none can
* be found
*/
private View findFocusableViewInBounds(boolean leftFocus, int left, int right) {
List<View> focusables = getFocusables(View.FOCUS_FORWARD);
View focusCandidate = null;
/*
* A fully contained focusable is one where its left is below the bound's
* left, and its right is above the bound's right. A partially
* contained focusable is one where some part of it is within the
* bounds, but it also has some part that is not within bounds. A fully contained
* focusable is preferred to a partially contained focusable.
*/
boolean foundFullyContainedFocusable = false;
int count = focusables.size();
for (int i = 0; i < count; i++) {
View view = focusables.get(i);
int viewLeft = view.getLeft();
int viewRight = view.getRight();
if (left < viewRight && viewLeft < right) {
/*
* the focusable is in the target area, it is a candidate for
* focusing
*/
final boolean viewIsFullyContained = (left < viewLeft) &&
(viewRight < right);
if (focusCandidate == null) {
/* No candidate, take this one */
focusCandidate = view;
foundFullyContainedFocusable = viewIsFullyContained;
} else {
final boolean viewIsCloserToBoundary =
(leftFocus && viewLeft < focusCandidate.getLeft()) ||
(!leftFocus && viewRight > focusCandidate.getRight());
if (foundFullyContainedFocusable) {
if (viewIsFullyContained && viewIsCloserToBoundary) {
/*
* We're dealing with only fully contained views, so
* it has to be closer to the boundary to beat our
* candidate
*/
focusCandidate = view;
}
} else {
if (viewIsFullyContained) {
/* Any fully contained view beats a partially contained view */
focusCandidate = view;
foundFullyContainedFocusable = true;
} else if (viewIsCloserToBoundary) {
/*
* Partially contained view beats another partially
* contained view if it's closer
*/
focusCandidate = view;
}
}
}
}
}
return focusCandidate;
}
/**
* <p>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.</p>
*
* @param direction the scroll direction: {@link android.view.View#FOCUS_LEFT}
* to go one page left or {@link android.view.View#FOCUS_RIGHT}
* to go one page right
* @return true if the key event is consumed by this method, false otherwise
*/
public boolean pageScroll(int direction) {
boolean right = direction == View.FOCUS_RIGHT;
int width = getWidth();
if (right) {
mTempRect.left = getScrollX() + width;
int count = getChildCount();
if (count > 0) {
View view = getChildAt(0);
if (mTempRect.left + width > view.getRight()) {
mTempRect.left = view.getRight() - width;
}
}
} else {
mTempRect.left = getScrollX() - width;
if (mTempRect.left < 0) {
mTempRect.left = 0;
}
}
mTempRect.right = mTempRect.left + width;
return scrollAndFocus(direction, mTempRect.left, mTempRect.right);
}
/**
* <p>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.</p>
*
* @param direction the scroll direction: {@link android.view.View#FOCUS_LEFT}
* to go the left of the view or {@link android.view.View#FOCUS_RIGHT}
* to go the right
* @return true if the key event is consumed by this method, false otherwise
*/
public boolean fullScroll(int direction) {
boolean right = direction == View.FOCUS_RIGHT;
int width = getWidth();
mTempRect.left = 0;
mTempRect.right = width;
if (right) {
int count = getChildCount();
if (count > 0) {
View view = getChildAt(0);
mTempRect.right = view.getRight();
mTempRect.left = mTempRect.right - width;
}
}
return scrollAndFocus(direction, mTempRect.left, mTempRect.right);
}
/**
* <p>Scrolls the view to make the area defined by
Other Android examples (source code examples)Here is a short list of links related to this Android HorizontalScrollView.java source code file: |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.