android 弹性scrollview,[自定义View]有弹性的ScrollwView

package com.gzgd.myapplication4;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Rect;

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.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 android.widget.EdgeEffect;

import android.widget.FrameLayout;

import android.widget.OverScroller;

import android.widget.ScrollView;

import java.util.List;

/**

*翻页有反弹效果

Created by runrun on 2018/4/4.

*/

public class MScrollView extends FrameLayout {

static final int ANIMATED_SCROLL_GAP = 200;

static final float MAX_SCROLL_FACTOR = 0.5f;

private static final String TAG = "MScrollView";

private long mLastScroll;

protected final Rect mTempRect = new Rect();

private OverScroller mScroller;

private EdgeEffect mEdgeGlowTop;

private EdgeEffect mEdgeGlowBottom;

/**

* 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;

/**

* 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 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 MScrollView.SavedState mSavedState;

private Context mContext;

public MScrollView(Context context) {

this(context, null);

}

public MScrollView(Context context, AttributeSet attrs) {

this(context, attrs, com.android.internal.R.attr.scrollViewStyle);

}

public MScrollView(Context context, AttributeSet attrs, int defStyleAttr) {

this(context, attrs, defStyleAttr, 0);

}

public MScrollView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {

super(context, attrs, defStyleAttr, defStyleRes);

mContext = context;

initScrollView();

final TypedArray a = context.obtainStyledAttributes(

attrs, com.android.internal.R.styleable.ScrollView, defStyleAttr, defStyleRes);

setFillViewport(a.getBoolean(com.android.internal.R.styleable.ScrollView_fillViewport, false));

a.recycle();

}

@Override

public boolean shouldDelayChildPressedState() {

return true;

}

@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 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 getMaxScrollAmount() {

return (int) (MAX_SCROLL_FACTOR * (getBottom() - getTop()));

}

private void initScrollView() {

mScroller = new OverScroller(getContext());

setFocusable(true);

setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);

setWillNotDraw(false);

final ViewConfiguration configuration = ViewConfiguration.get(mContext);

mOverflingDistance = configuration.getScaledOverflingDistance();

}

@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

*/

protected boolean canScroll() {

View child = getChildAt(0);

if (child != null) {

int childHeight = child.getHeight();

return getHeight() < childHeight + getPaddingTop() + getPaddingBottom();

}

return false;

}

/**

* Indicates whether this ScrollView's content is stretched to fill the viewport.

*

* @return True if the content fills the viewport, false otherwise.

* @attr ref android.R.styleable#ScrollView_fillViewport

*/

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.

* @attr ref android.R.styleable#ScrollView_fillViewport

*/

public void setFillViewport(boolean fillViewport) {

if (fillViewport != mFillViewport) {

mFillViewport = fillViewport;

requestLayout();

}

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

if (!mFillViewport) {

return;

}

final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

if (heightMode == MeasureSpec.UNSPECIFIED) {

return;

}

if (getChildCount() > 0) {

final View child = getChildAt(0);

int height = getMeasuredHeight();

if (child.getMeasuredHeight() < height) {

final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();

int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,

getPaddingLeft() + getPaddingRight(), lp.width);

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

boolean isdis = super.dispatchKeyEvent(event);

Log.i(TAG, "---------- dispatchKeyEvent : " + isdis);

return isdis || 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() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) {

View currentFocused = findFocus();

if (currentFocused == this) currentFocused = null;

View nextFocused = FocusFinder.getInstance().findNextFocus(this,

currentFocused, View.FOCUS_DOWN);

return nextFocused != null

&& nextFocused != this

&& nextFocused.requestFocus(View.FOCUS_DOWN);

}

return false;

}

boolean handled = false;

if (event.getAction() == KeyEvent.ACTION_DOWN) {

switch (event.getKeyCode()) {

case KeyEvent.KEYCODE_DPAD_UP:

if (!event.isAltPressed()) {

handled = arrowScroll(View.FOCUS_UP);

} else {

handled = fullScroll(View.FOCUS_UP);

}

break;

case KeyEvent.KEYCODE_DPAD_DOWN:

if (!event.isAltPressed()) {

handled = arrowScroll(View.FOCUS_DOWN);

} else {

handled = fullScroll(View.FOCUS_DOWN);

}

break;

case KeyEvent.KEYCODE_SPACE:

pageScroll(event.isShiftPressed() ? View.FOCUS_UP : View.FOCUS_DOWN);

break;

}

}

return handled;

}

private boolean inChild(int x, int y) {

if (getChildCount() > 0) {

final int scrollY = getScrollY();

final View child = getChildAt(0);

return !(y < child.getTop() - scrollY

|| y >= child.getBottom() - scrollY

|| x < child.getLeft()

|| x >= child.getRight());

}

return false;

}

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) {

return true;

}

@Override

public boolean onTouchEvent(MotionEvent ev) {

return true;

}

@Override

public boolean onGenericMotionEvent(MotionEvent event) {

return super.onGenericMotionEvent(event);

}

@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 = getScrollX();

final int oldY = getScrollY();

setScrollX(scrollX);

setScrollY(scrollY);

invalidateParentIfNeeded();

onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);

if (clampedY) {

mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange());

}

} else {

super.scrollTo(scrollX, scrollY);

}

// awakenScrollBars();

}

protected void invalidateParentIfNeeded() {

if (isHardwareAccelerated() && getParent() instanceof View) {

((View) getParent()).invalidate();

}

}

@Override

public boolean performAccessibilityAction(int action, Bundle arguments) {

if (super.performAccessibilityAction(action, arguments)) {

return true;

}

if (!isEnabled()) {

return false;

}

switch (action) {

case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {

final int viewportHeight = getHeight() - getPaddingBottom() - getPaddingTop();

final int targetScrollY = Math.min(getScrollY() + viewportHeight, getScrollRange());

if (targetScrollY != getScrollY()) {

smoothScrollTo(0, targetScrollY);

return true;

}

}

return false;

case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {

final int viewportHeight = getHeight() - getPaddingBottom() - getPaddingTop();

final int targetScrollY = Math.max(getScrollY() - viewportHeight, 0);

if (targetScrollY != getScrollY()) {

smoothScrollTo(0, targetScrollY);

return true;

}

}

return false;

}

return false;

}

@Override

public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {

super.onInitializeAccessibilityNodeInfo(info);

info.setClassName(ScrollView.class.getName());

if (isEnabled()) {

final int scrollRange = getScrollRange();

if (scrollRange > 0) {

info.setScrollable(true);

if (getScrollY() > 0) {

info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);

}

if (getScrollY() < scrollRange) {

info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);

}

}

}

}

@Override

public void onInitializeAccessibilityEvent(AccessibilityEvent event) {

super.onInitializeAccessibilityEvent(event);

event.setClassName(ScrollView.class.getName());

final boolean scrollable = getScrollRange() > 0;

event.setScrollable(scrollable);

event.setScrollX(getScrollX());

event.setScrollY(getScrollY());

event.setMaxScrollX(getScrollX());

event.setMaxScrollY(getScrollRange());

}

private int getScrollRange() {

int scrollRange = 0;

if (getChildCount() > 0) {

View child = getChildAt(0);

scrollRange = Math.max(0,

child.getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop()));

}

return scrollRange;

}

/**

*

* Finds the next focusable component that fits in the specified bounds.

*

*

* @param topFocus look for a candidate is the one at the top of the bounds

* if topFocus is true, or at the bottom of the bounds if topFocus is

* false

* @param top the top offset of the bounds in which a focusable must be

* found

* @param bottom the bottom 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 topFocus, int top, int bottom) {

List focusables = getFocusables(View.FOCUS_FORWARD);

View focusCandidate = null;

/*

* A fully contained focusable is one where its top is below the bound's

* top, and its bottom is above the bound's bottom. 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 viewTop = view.getTop();

int viewBottom = view.getBottom();

if (top < viewBottom && viewTop < bottom) {

/*

* the focusable is in the target area, it is a candidate for

* focusing

*/

final boolean viewIsFullyContained = (top < viewTop) &&

(viewBottom < bottom);

if (focusCandidate == null) {

/* No candidate, take this one */

focusCandidate = view;

foundFullyContainedFocusable = viewIsFullyContained;

} else {

final boolean viewIsCloserToBoundary =

(topFocus && viewTop < focusCandidate.getTop()) ||

(!topFocus && viewBottom > focusCandidate

.getBottom());

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;

}

/**

*

Handles scrolling in response to a "page up/down" shortcut press. This

* method will scroll the view by one page up or down and give the focus

* to the topmost/bottommost component in the new visible area. If no

* component is a good candidate for focus, this scrollview reclaims the

* focus.

*

* @param direction the scroll direction: {@link android.view.View#FOCUS_UP}

* to go one page up or

* {@link android.view.View#FOCUS_DOWN} to go one page down

* @return true if the key event is consumed by this method, false otherwise

*/

public boolean pageScroll(int direction) {

boolean down = direction == View.FOCUS_DOWN;

int height = getHeight();

if (down) {

mTempRect.top = getScrollY() + height;

int count = getChildCount();

if (count > 0) {

View view = getChildAt(count - 1);

if (mTempRect.top + height > view.getBottom()) {

mTempRect.top = view.getBottom() - height;

}

}

} else {

mTempRect.top = getScrollY() - height;

if (mTempRect.top < 0) {

mTempRect.top = 0;

}

}

mTempRect.bottom = mTempRect.top + height;

return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);

}

/**

*

Handles scrolling in response to a "home/end" shortcut press. This

* method will scroll the view to the top or bottom and give the focus

* to the topmost/bottommost component in the new visible area. If no

* component is a good candidate for focus, this scrollview reclaims the

* focus.

*

* @param direction the scroll direction: {@link android.view.View#FOCUS_UP}

* to go the top of the view or

* {@link android.view.View#FOCUS_DOWN} to go the bottom

* @return true if the key event is consumed by this method, false otherwise

*/

public boolean fullScroll(int direction) {

boolean down = direction == View.FOCUS_DOWN;

int height = getHeight();

mTempRect.top = 0;

mTempRect.bottom = height;

if (down) {

int count = getChildCount();

if (count > 0) {

View view = getChildAt(count - 1);

mTempRect.bottom = view.getBottom() + getPaddingBottom();

mTempRect.top = mTempRect.bottom - height;

}

}

return scrollAndFocus(direction, mTempRect.top, mTempRect.bottom);

}

/**

*

Scrolls the view to make the area defined by top and

* bottom 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.

*

* @param direction the scroll direction: {@link android.view.View#FOCUS_UP}

* to go upward, {@link android.view.View#FOCUS_DOWN} to downward

* @param top the top offset of the new area to be made visible

* @param bottom the bottom offset of the new area to be made visible

* @return true if the key event is consumed by this method, false otherwise

*/

private boolean scrollAndFocus(int direction, int top, int bottom) {

boolean handled = true;

int height = getHeight();

int containerTop = getScrollY();

int containerBottom = containerTop + height;

boolean up = direction == View.FOCUS_UP;

View newFocused = findFocusableViewInBounds(up, top, bottom);

if (newFocused == null) {

newFocused = this;

}

if (top >= containerTop && bottom <= containerBottom) {

handled = false;

} else {

int delta = up ? (top - containerTop) : (bottom - containerBottom);

doScrollY(delta);

}

if (newFocused != findFocus()) newFocused.requestFocus(direction);

return handled;

}

/**

* Handle scrolling in response to an up or down arrow click.

*

* @param direction The direction corresponding to the arrow key that was

* pressed

* @return True if we consumed the event, false otherwise

*/

protected boolean arrowScroll(int direction) {

View currentFocused = findFocus();

if (currentFocused == this) currentFocused = null;

View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);

final int maxJump = getMaxScrollAmount();

if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump, getHeight())) {

nextFocused.getDrawingRect(mTempRect);

offsetDescendantRectToMyCoords(nextFocused, mTempRect);

int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);

doScrollY(scrollDelta);

nextFocused.requestFocus(direction);

} else {

// no new focus

int scrollDelta = maxJump;

if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) {

scrollDelta = getScrollY();

} else if (direction == View.FOCUS_DOWN) {

if (getChildCount() > 0) {

int daBottom = getChildAt(0).getBottom();

int screenBottom = getScrollY() + getHeight() - getPaddingBottom();

if (daBottom - screenBottom < maxJump) {

scrollDelta = daBottom - screenBottom;

}

}

}

if (scrollDelta == 0) {

return false;

}

doScrollY(direction == View.FOCUS_DOWN ? scrollDelta : -scrollDelta);

}

if (currentFocused != null && currentFocused.isFocused()

&& isOffScreen(currentFocused)) {

// previously focused item still has focus and is off screen, give

// it up (take it back to ourselves)

// (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are

// sure to

// get it)

final int descendantFocusability = getDescendantFocusability(); // save

setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);

requestFocus();

setDescendantFocusability(descendantFocusability); // restore

}

return true;

}

/**

* @return whether the descendant of this scroll view is scrolled off

* screen.

*/

protected boolean isOffScreen(View descendant) {

return !isWithinDeltaOfScreen(descendant, 0, getHeight());

}

/**

* @return whether the descendant of this scroll view is within delta

* pixels of being on the screen.

*/

protected boolean isWithinDeltaOfScreen(View descendant, int delta, int height) {

descendant.getDrawingRect(mTempRect);

offsetDescendantRectToMyCoords(descendant, mTempRect);

return (mTempRect.bottom + delta) >= getScrollY()

&& (mTempRect.top - delta) <= (getScrollY() + height);

}

/**

* Smooth scroll by a Y delta

*

* @param delta the number of pixels to scroll by on the Y axis

*/

protected void doScrollY(int delta) {

if (delta != 0) {

if (mSmoothScrollingEnabled) {

smoothScrollBy(0, delta);

} else {

scrollBy(0, delta);

}

}

}

/**

* Like {@link View#scrollBy}, but scroll smoothly instead of immediately.

*

* @param dx the number of pixels to scroll by on the X axis

* @param dy the number of pixels to scroll by on the Y axis

*/

public final void smoothScrollBy(int dx, int dy) {

if (getChildCount() == 0) {

return;

}

if (!mScroller.isFinished()) {

mScroller.abortAnimation();

}

final int height = getHeight() - getPaddingBottom() - getPaddingTop();

final int bottom = getChildAt(0).getHeight();

final int maxY = Math.max(0, bottom - height);

final int scrollY = getScrollY();

dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY;

mScroller.startScroll(getScrollX(), scrollY, dx, dy, computeTime(dy));

postInvalidateOnAnimation();

}

private int computeTime(int d) {

int time = Math.abs(d);

if (time < ANIMATED_SCROLL_GAP) {

time = ANIMATED_SCROLL_GAP;

} else {

double index = 1 + Math.pow(((float) ANIMATED_SCROLL_GAP / time), time / ANIMATED_SCROLL_GAP);

time = (int) (ANIMATED_SCROLL_GAP * index);

}

return time;

}

/**

* Like {@link #scrollTo}, but scroll smoothly instead of immediately.

*

* @param x the position where to scroll on the X axis

* @param y the position where to scroll on the Y axis

*/

public final void smoothScrollTo(int x, int y) {

smoothScrollBy(x - getScrollX(), y - getScrollY());

}

/**

*

The scroll range of a scroll view is the overall height of all of its

* children.

*/

@Override

protected int computeVerticalScrollRange() {

final int count = getChildCount();

final int contentHeight = getHeight() - getPaddingBottom() - getPaddingTop();

if (count == 0) {

return contentHeight;

}

int scrollRange = getChildAt(0).getBottom();

final int scrollY = getScrollY();

final int overscrollBottom = Math.max(0, scrollRange - contentHeight);

if (scrollY < 0) {

scrollRange -= scrollY;

} else if (scrollY > overscrollBottom) {

scrollRange += scrollY - overscrollBottom;

}

return scrollRange;

}

@Override

protected int computeVerticalScrollOffset() {

return Math.max(0, super.computeVerticalScrollOffset());

}

@Override

protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {

ViewGroup.LayoutParams lp = child.getLayoutParams();

int childWidthMeasureSpec;

int childHeightMeasureSpec;

childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, getPaddingLeft()

+ getPaddingRight(), lp.width);

childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

@Override

protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,

int parentHeightMeasureSpec, int heightUsed) {

final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,

getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin

+ widthUsed, lp.width);

final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(

lp.topMargin + lp.bottomMargin, MeasureSpec.UNSPECIFIED);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

@Override

public void computeScroll() {

if (mScroller.computeScrollOffset()) {

int oldX = getScrollX();

int oldY = getScrollY();

int x = mScroller.getCurrX();

int y = mScroller.getCurrY();

if (oldX != x || oldY != y) {

final int range = getScrollRange();

final int overscrollMode = getOverScrollMode();

final boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS ||

(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0);

overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,

0, mOverflingDistance, false);

// onScrollChanged(getScrollX(), getScrollY(), oldX, oldY);

// if (canOverscroll) {

// if (y < 0 && oldY >= 0) {

// mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());

// } else if (y > range && oldY <= range) {

// mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());

// }

// }

}

// if (!awakenScrollBars()) {

// Keep on drawing until the animation has finished.

// postInvalidateOnAnimation();

// }

} else

{

}

}

/**

* Scrolls the view to the given child.

*

* @param child the View to scroll to

*/

private void scrollToChild(View child) {

child.getDrawingRect(mTempRect);

/* Offset from child's local coordinates to ScrollView coordinates */

offsetDescendantRectToMyCoords(child, mTempRect);

int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);

if (scrollDelta != 0) {

scrollBy(0, scrollDelta);

}

}

/**

* If rect is off screen, scroll just enough to get it (or at least the

* first screen size chunk of it) on screen.

*

* @param rect The rectangle.

* @param immediate True to scroll immediately without animation

* @return true if scrolling was performed

*/

private boolean scrollToChildRect(Rect rect, boolean immediate) {

final int delta = computeScrollDeltaToGetChildRectOnScreen(rect);

final boolean scroll = delta != 0;

if (scroll) {

if (immediate) {

scrollBy(0, delta);

} else {

smoothScrollBy(0, delta);

}

}

return scroll;

}

/**

* Compute the amount to scroll in the Y direction in order to get

* a rectangle completely on the screen (or, if taller than the screen,

* at least the first screen size chunk of it).

*

* @param rect The rect.

* @return The scroll delta.

*/

protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {

if (getChildCount() == 0) return 0;

int height = getHeight();

int screenTop = getScrollY();

int screenBottom = screenTop + height;

int fadingEdge = getVerticalFadingEdgeLength();

// leave room for top fading edge as long as rect isn't at very top

if (rect.top > 0) {

screenTop += fadingEdge;

}

// leave room for bottom fading edge as long as rect isn't at very bottom

if (rect.bottom < getChildAt(0).getHeight()) {

screenBottom -= fadingEdge;

}

int scrollYDelta = 0;

if (rect.bottom > screenBottom && rect.top > screenTop) {

// need to move down to get it in view: move down just enough so

// that the entire rectangle is in view (or at least the first

// screen size chunk).

if (rect.height() > height) {

// just enough to get screen size chunk on

scrollYDelta += (rect.top - screenTop);

} else {

// get entire rect at bottom of screen

scrollYDelta += (rect.bottom - screenBottom);

}

// make sure we aren't scrolling beyond the end of our content

int bottom = getChildAt(0).getBottom();

int distanceToBottom = bottom - screenBottom;

scrollYDelta = Math.min(scrollYDelta, distanceToBottom);

} else if (rect.top < screenTop && rect.bottom < screenBottom) {

// need to move up to get it in view: move up just enough so that

// entire rectangle is in view (or at least the first screen

// size chunk of it).

if (rect.height() > height) {

// screen size chunk

scrollYDelta -= (screenBottom - rect.bottom);

} else {

// entire rect at top

scrollYDelta -= (screenTop - rect.top);

}

// make sure we aren't scrolling any further than the top our content

scrollYDelta = Math.max(scrollYDelta, -getScrollY());

}

return scrollYDelta;

}

@Override

public void requestChildFocus(View child, View focused) {

if (!mIsLayoutDirty) {

scrollToChild(focused);

} else {

// The child may not be laid out yet, we can't compute the scroll yet

mChildToScrollTo = focused;

}

super.requestChildFocus(child, focused);

}

/**

* When looking for focus in children of a scroll view, need to be a little

* more careful not to give focus to something that is scrolled off screen.

*

* This is more expensive than the default {@link android.view.ViewGroup}

* implementation, otherwise this behavior might have been made the default.

*/

@Override

protected boolean onRequestFocusInDescendants(int direction,

Rect previouslyFocusedRect) {

// convert from forward / backward notation to up / down / left / right

// (ugh).

if (direction == View.FOCUS_FORWARD) {

direction = View.FOCUS_DOWN;

} else if (direction == View.FOCUS_BACKWARD) {

direction = View.FOCUS_UP;

}

final View nextFocus = previouslyFocusedRect == null ?

FocusFinder.getInstance().findNextFocus(this, null, direction) :

FocusFinder.getInstance().findNextFocusFromRect(this,

previouslyFocusedRect, direction);

if (nextFocus == null) {

return false;

}

if (isOffScreen(nextFocus)) {

return false;

}

return nextFocus.requestFocus(direction, previouslyFocusedRect);

}

@Override

public boolean requestChildRectangleOnScreen(View child, Rect rectangle,

boolean immediate) {

// offset into coordinate space of this scroll view

rectangle.offset(child.getLeft() - child.getScrollX(),

child.getTop() - child.getScrollY());

return scrollToChildRect(rectangle, immediate);

}

@Override

public void requestLayout() {

mIsLayoutDirty = true;

super.requestLayout();

}

@Override

protected void onDetachedFromWindow() {

super.onDetachedFromWindow();

}

@Override

protected void onLayout(boolean changed, int l, int t, int r, int b) {

super.onLayout(changed, l, t, r, b);

if (!changed) {

return;

}

mIsLayoutDirty = false;

// Give a child focus if it needs it

if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) {

scrollToChild(mChildToScrollTo);

}

mChildToScrollTo = null;

if (!isLaidOut()) {

if (mSavedState != null) {

setScrollY(mSavedState.scrollPosition);

mSavedState = null;

} // mScrollY default value is "0"

final int childHeight = (getChildCount() > 0) ? getChildAt(0).getMeasuredHeight() : 0;

final int scrollRange = Math.max(0,

childHeight - (b - t - getPaddingBottom() - getPaddingTop()));

// Don't forget to clamp

if (getScrollY() > scrollRange) {

setScrollY(scrollRange);

} else if (getScrollY() < 0) {

setScrollY(0);

}

}

// Calling this with the present values causes it to re-claim them

scrollTo(getScrollX(), getScrollY());

}

@Override

protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

View currentFocused = findFocus();

if (null == currentFocused || this == currentFocused)

return;

// If the currently-focused view was visible on the screen when the

// screen was at the old height, then scroll the screen to make that

// view visible with the new screen height.

if (isWithinDeltaOfScreen(currentFocused, 0, oldh)) {

currentFocused.getDrawingRect(mTempRect);

offsetDescendantRectToMyCoords(currentFocused, mTempRect);

int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect);

doScrollY(scrollDelta);

}

}

/**

* Return true if child is a descendant of parent, (or equal to the parent).

*/

private static boolean isViewDescendantOf(View child, View parent) {

if (child == parent) {

return true;

}

final ViewParent theParent = child.getParent();

return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent);

}

/**

* Fling the scroll view

*

* @param velocityY The initial velocity in the Y direction. Positive

* numbers mean that the finger/cursor is moving down the screen,

* which means we want to scroll towards the top.

*/

public void fling(int velocityY) {

if (getChildCount() > 0) {

int height = getHeight() - getPaddingBottom() - getPaddingTop();

int bottom = getChildAt(0).getHeight();

mScroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0,

Math.max(0, bottom - height), 0, height / 2);

postInvalidateOnAnimation();

}

}

private void flingWithNestedDispatch(int velocityY) {

final boolean canFling = (getScrollY() > 0 || velocityY > 0) &&

(getScrollY() < getScrollRange() || velocityY < 0);

if (!dispatchNestedPreFling(0, velocityY)) {

dispatchNestedFling(0, velocityY, canFling);

if (canFling) {

fling(velocityY);

}

}

}

/**

* {@inheritDoc}

*

*

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() - getPaddingRight() - getPaddingLeft(), child.getWidth());

y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight());

if (x != getScrollX() || y != getScrollY()) {

super.scrollTo(x, y);

}

}

}

@Override

public void setOverScrollMode(int mode) {

if (mode != OVER_SCROLL_NEVER) {

if (mEdgeGlowTop == null) {

Context context = getContext();

mEdgeGlowTop = new EdgeEffect(context);

mEdgeGlowBottom = new EdgeEffect(context);

}

} else {

mEdgeGlowTop = null;

mEdgeGlowBottom = null;

}

super.setOverScrollMode(mode);

}

@Override

public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {

return (nestedScrollAxes & SCROLL_AXIS_VERTICAL) != 0;

}

@Override

public void onNestedScrollAccepted(View child, View target, int axes) {

super.onNestedScrollAccepted(child, target, axes);

startNestedScroll(SCROLL_AXIS_VERTICAL);

}

/**

* @inheritDoc

*/

@Override

public void onStopNestedScroll(View target) {

super.onStopNestedScroll(target);

}

@Override

public void onNestedScroll(View target, int dxConsumed, int dyConsumed,

int dxUnconsumed, int dyUnconsumed) {

final int oldScrollY = getScrollY();

scrollBy(0, dyUnconsumed);

final int myConsumed = getScrollY() - oldScrollY;

final int myUnconsumed = dyUnconsumed - myConsumed;

dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null);

}

/**

* @inheritDoc

*/

@Override

public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {

if (!consumed) {

flingWithNestedDispatch((int) velocityY);

return true;

}

return false;

}

// float time = 0;

// float currentTime = 0;

@Override

public void draw(Canvas canvas) {

super.draw(canvas);

if (mEdgeGlowTop != null) {

final int scrollY = getScrollY();

if (!mEdgeGlowTop.isFinished()) {

final int restoreCount = canvas.save();

final int width = getWidth() - getPaddingLeft() - getPaddingRight();

canvas.translate(getPaddingLeft(), Math.min(0, scrollY));

mEdgeGlowTop.setSize(width, getHeight());

if (mEdgeGlowTop.draw(canvas)) {

postInvalidateOnAnimation();

}

canvas.restoreToCount(restoreCount);

}

if (!mEdgeGlowBottom.isFinished()) {

final int restoreCount = canvas.save();

final int width = getWidth() - getPaddingLeft() - getPaddingRight();

final int height = getHeight();

canvas.translate(-width + getPaddingLeft(),

Math.max(getScrollRange(), scrollY) + height);

canvas.rotate(180, width, 0);

mEdgeGlowBottom.setSize(width, height);

if (mEdgeGlowBottom.draw(canvas)) {

postInvalidateOnAnimation();

}

canvas.restoreToCount(restoreCount);

}

}

// currentTime = System.currentTimeMillis();

// if (0 != time) {

// LogUtils.i(TAG, "onDraw: time : " + (currentTime - time));

// }

// time = currentTime;

}

private static int clamp(int n, int my, int child) {

if (my >= child || n < 0) {

/* my >= child is this case:

* |--------------- me ---------------|

* |------ child ------|

* or

* |--------------- me ---------------|

* |------ child ------|

* or

* |--------------- me ---------------|

* |------ child ------|

*

* n < 0 is this case:

* |------ me ------|

* |-------- child --------|

* |-- mScrollX --|

*/

return 0;

}

if ((my + n) > child) {

/* this case:

* |------ me ------|

* |------ child ------|

* |-- mScrollX --|

*/

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;

}

MScrollView.SavedState ss = (MScrollView.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();

MScrollView.SavedState ss = new MScrollView.SavedState(superState);

ss.scrollPosition = getScrollY();

return ss;

}

static class SavedState extends BaseSavedState {

public int scrollPosition;

SavedState(Parcelable superState) {

super(superState);

}

public SavedState(Parcel source) {

super(source);

scrollPosition = source.readInt();

}

@Override

public void writeToParcel(Parcel dest, int flags) {

super.writeToParcel(dest, flags);

dest.writeInt(scrollPosition);

}

@Override

public String toString() {

return "HorizontalScrollView.SavedState{"

+ Integer.toHexString(System.identityHashCode(this))

+ " scrollPosition=" + scrollPosition + "}";

}

public static final Parcelable.Creator CREATOR

= new Parcelable.Creator() {

public MScrollView.SavedState createFromParcel(Parcel in) {

return new MScrollView.SavedState(in);

}

public MScrollView.SavedState[] newArray(int size) {

return new MScrollView.SavedState[size];

}

};

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值