Source code
package android.support.v4.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Parcelable.Creator;
import android.support.v4.media.TransportMediator;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ScrollingView;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.FocusFinder;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.View.BaseSavedState;
import android.view.View.MeasureSpec;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import java.util.List;
public class NestedScrollView extends FrameLayout implements NestedScrollingParent, NestedScrollingChild, ScrollingView {
private static final AccessibilityDelegate ACCESSIBILITY_DELEGATE = new AccessibilityDelegate();
static final int ANIMATED_SCROLL_GAP = 250;
private static final int INVALID_POINTER = -1;
static final float MAX_SCROLL_FACTOR = 0.5f;
private static final int[] SCROLLVIEW_STYLEABLE = new int[]{16843130};
private static final String TAG = "NestedScrollView";
private int mActivePointerId;
private final NestedScrollingChildHelper mChildHelper;
private View mChildToScrollTo;
private EdgeEffectCompat mEdgeGlowBottom;
private EdgeEffectCompat mEdgeGlowTop;
private boolean mFillViewport;
private boolean mIsBeingDragged;
private boolean mIsLaidOut;
private boolean mIsLayoutDirty;
private int mLastMotionY;
private long mLastScroll;
private int mMaximumVelocity;
private int mMinimumVelocity;
private int mNestedYOffset;
private OnScrollChangeListener mOnScrollChangeListener;
private final NestedScrollingParentHelper mParentHelper;
private SavedState mSavedState;
private final int[] mScrollConsumed;
private final int[] mScrollOffset;
private ScrollerCompat mScroller;
private boolean mSmoothScrollingEnabled;
private final Rect mTempRect;
private int mTouchSlop;
private VelocityTracker mVelocityTracker;
private float mVerticalScrollFactor;
public interface OnScrollChangeListener {
void onScrollChange(NestedScrollView nestedScrollView, int i, int i2, int i3, int i4);
}
static class SavedState extends BaseSavedState {
public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
public int scrollPosition;
SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
this.scrollPosition = source.readInt();
}
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(this.scrollPosition);
}
public String toString() {
return "HorizontalScrollView.SavedState{" + Integer.toHexString(System.identityHashCode(this)) + " scrollPosition=" + this.scrollPosition + "}";
}
}
static class AccessibilityDelegate extends AccessibilityDelegateCompat {
AccessibilityDelegate() {
}
public boolean performAccessibilityAction(View host, int action, Bundle arguments) {
if (super.performAccessibilityAction(host, action, arguments)) {
return true;
}
NestedScrollView nsvHost = (NestedScrollView) host;
if (!nsvHost.isEnabled()) {
return false;
}
int targetScrollY;
switch (action) {
case 4096:
targetScrollY = Math.min(nsvHost.getScrollY() + ((nsvHost.getHeight() - nsvHost.getPaddingBottom()) - nsvHost.getPaddingTop()), nsvHost.getScrollRange());
if (targetScrollY == nsvHost.getScrollY()) {
return false;
}
nsvHost.smoothScrollTo(0, targetScrollY);
return true;
case 8192:
targetScrollY = Math.max(nsvHost.getScrollY() - ((nsvHost.getHeight() - nsvHost.getPaddingBottom()) - nsvHost.getPaddingTop()), 0);
if (targetScrollY == nsvHost.getScrollY()) {
return false;
}
nsvHost.smoothScrollTo(0, targetScrollY);
return true;
default:
return false;
}
}
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
super.onInitializeAccessibilityNodeInfo(host, info);
NestedScrollView nsvHost = (NestedScrollView) host;
info.setClassName(ScrollView.class.getName());
if (nsvHost.isEnabled()) {
int scrollRange = nsvHost.getScrollRange();
if (scrollRange > 0) {
info.setScrollable(true);
if (nsvHost.getScrollY() > 0) {
info.addAction(8192);
}
if (nsvHost.getScrollY() < scrollRange) {
info.addAction(4096);
}
}
}
}
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(host, event);
NestedScrollView nsvHost = (NestedScrollView) host;
event.setClassName(ScrollView.class.getName());
AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
record.setScrollable(nsvHost.getScrollRange() > 0);
record.setScrollX(nsvHost.getScrollX());
record.setScrollY(nsvHost.getScrollY());
record.setMaxScrollX(nsvHost.getScrollX());
record.setMaxScrollY(nsvHost.getScrollRange());
}
}
public NestedScrollView(Context context) {
this(context, null);
}
public NestedScrollView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.mTempRect = new Rect();
this.mIsLayoutDirty = true;
this.mIsLaidOut = false;
this.mChildToScrollTo = null;
this.mIsBeingDragged = false;
this.mSmoothScrollingEnabled = true;
this.mActivePointerId = -1;
this.mScrollOffset = new int[2];
this.mScrollConsumed = new int[2];
initScrollView();
TypedArray a = context.obtainStyledAttributes(attrs, SCROLLVIEW_STYLEABLE, defStyleAttr, 0);
setFillViewport(a.getBoolean(0, false));
a.recycle();
this.mParentHelper = new NestedScrollingParentHelper(this);
this.mChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
ViewCompat.setAccessibilityDelegate(this, ACCESSIBILITY_DELEGATE);
}
public void setNestedScrollingEnabled(boolean enabled) {
this.mChildHelper.setNestedScrollingEnabled(enabled);
}
public boolean isNestedScrollingEnabled() {
return this.mChildHelper.isNestedScrollingEnabled();
}
public boolean startNestedScroll(int axes) {
return this.mChildHelper.startNestedScroll(axes);
}
public void stopNestedScroll() {
this.mChildHelper.stopNestedScroll();
}
public boolean hasNestedScrollingParent() {
return this.mChildHelper.hasNestedScrollingParent();
}
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
return this.mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
return this.mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
return this.mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
}
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return this.mChildHelper.dispatchNestedPreFling(velocityX, velocityY);
}
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
return (nestedScrollAxes & 2) != 0;
}
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
this.mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
startNestedScroll(2);
}
public void onStopNestedScroll(View target) {
stopNestedScroll();
}
public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
int oldScrollY = getScrollY();
scrollBy(0, dyUnconsumed);
int myConsumed = getScrollY() - oldScrollY;
dispatchNestedScroll(0, myConsumed, 0, dyUnconsumed - myConsumed, null);
}
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
}
public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
if (consumed) {
return false;
}
flingWithNestedDispatch((int) velocityY);
return true;
}
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
return false;
}
public int getNestedScrollAxes() {
return this.mParentHelper.getNestedScrollAxes();
}
public boolean shouldDelayChildPressedState() {
return true;
}
protected float getTopFadingEdgeStrength() {
if (getChildCount() == 0) {
return 0.0f;
}
int length = getVerticalFadingEdgeLength();
int scrollY = getScrollY();
if (scrollY < length) {
return ((float) scrollY) / ((float) length);
}
return 1.0f;
}
protected float getBottomFadingEdgeStrength() {
if (getChildCount() == 0) {
return 0.0f;
}
int length = getVerticalFadingEdgeLength();
int span = (getChildAt(0).getBottom() - getScrollY()) - (getHeight() - getPaddingBottom());
if (span < length) {
return ((float) span) / ((float) length);
}
return 1.0f;
}
public int getMaxScrollAmount() {
return (int) (MAX_SCROLL_FACTOR * ((float) getHeight()));
}
private void initScrollView() {
this.mScroller = new ScrollerCompat(getContext(), null);
setFocusable(true);
setDescendantFocusability(262144);
setWillNotDraw(false);
ViewConfiguration configuration = ViewConfiguration.get(getContext());
this.mTouchSlop = configuration.getScaledTouchSlop();
this.mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
this.mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}
public void addView(View child) {
if (getChildCount() > 0) {
throw new IllegalStateException("ScrollView can host only one direct child");
}
super.addView(child);
}
public void addView(View child, int index) {
if (getChildCount() > 0) {
throw new IllegalStateException("ScrollView can host only one direct child");
}
super.addView(child, index);
}
public void addView(View child, LayoutParams params) {
if (getChildCount() > 0) {
throw new IllegalStateException("ScrollView can host only one direct child");
}
super.addView(child, params);
}
public void addView(View child, int index, LayoutParams params) {
if (getChildCount() > 0) {
throw new IllegalStateException("ScrollView can host only one direct child");
}
super.addView(child, index, params);
}
public void setOnScrollChangeListener(OnScrollChangeListener l) {
this.mOnScrollChangeListener = l;
}
private boolean canScroll() {
View child = getChildAt(0);
if (child == null) {
return false;
}
if (getHeight() < (getPaddingTop() + child.getHeight()) + getPaddingBottom()) {
return true;
}
return false;
}
public boolean isFillViewport() {
return this.mFillViewport;
}
public void setFillViewport(boolean fillViewport) {
if (fillViewport != this.mFillViewport) {
this.mFillViewport = fillViewport;
requestLayout();
}
}
public boolean isSmoothScrollingEnabled() {
return this.mSmoothScrollingEnabled;
}
public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {
this.mSmoothScrollingEnabled = smoothScrollingEnabled;
}
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (this.mOnScrollChangeListener != null) {
this.mOnScrollChangeListener.onScrollChange(this, l, t, oldl, oldt);
}
}
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (this.mFillViewport && MeasureSpec.getMode(heightMeasureSpec) != 0 && getChildCount() > 0) {
View child = getChildAt(0);
int height = getMeasuredHeight();
if (child.getMeasuredHeight() < height) {
child.measure(getChildMeasureSpec(widthMeasureSpec, getPaddingLeft() + getPaddingRight(), ((FrameLayout.LayoutParams) child.getLayoutParams()).width), MeasureSpec.makeMeasureSpec((height - getPaddingTop()) - getPaddingBottom(), 1073741824));
}
}
}
public boolean dispatchKeyEvent(KeyEvent event) {
return super.dispatchKeyEvent(event) || executeKeyEvent(event);
}
public boolean executeKeyEvent(KeyEvent event) {
this.mTempRect.setEmpty();
if (canScroll()) {
boolean handled = false;
if (event.getAction() == 0) {
switch (event.getKeyCode()) {
case 19:
if (!event.isAltPressed()) {
handled = arrowScroll(33);
break;
}
handled = fullScroll(33);
break;
case 20:
if (!event.isAltPressed()) {
handled = arrowScroll(TransportMediator.KEYCODE_MEDIA_RECORD);
break;
}
handled = fullScroll(TransportMediator.KEYCODE_MEDIA_RECORD);
break;
case 62:
pageScroll(event.isShiftPressed() ? 33 : TransportMediator.KEYCODE_MEDIA_RECORD);
break;
}
}
return handled;
} else if (!isFocused() || event.getKeyCode() == 4) {
return false;
} else {
View currentFocused = findFocus();
if (currentFocused == this) {
currentFocused = null;
}
View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, TransportMediator.KEYCODE_MEDIA_RECORD);
if (nextFocused == null || nextFocused == this || !nextFocused.requestFocus(TransportMediator.KEYCODE_MEDIA_RECORD)) {
return false;
}
return true;
}
}
private boolean inChild(int x, int y) {
if (getChildCount() <= 0) {
return false;
}
int scrollY = getScrollY();
View child = getChildAt(0);
if (y < child.getTop() - scrollY || y >= child.getBottom() - scrollY || x < child.getLeft() || x >= child.getRight()) {
return false;
}
return true;
}
private void initOrResetVelocityTracker() {
if (this.mVelocityTracker == null) {
this.mVelocityTracker = VelocityTracker.obtain();
} else {
this.mVelocityTracker.clear();
}
}
private void initVelocityTrackerIfNotExists() {
if (this.mVelocityTracker == null) {
this.mVelocityTracker = VelocityTracker.obtain();
}
}
private void recycleVelocityTracker() {
if (this.mVelocityTracker != null) {
this.mVelocityTracker.recycle();
this.mVelocityTracker = null;
}
}
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept) {
recycleVelocityTracker();
}
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
boolean z = false;
int action = ev.getAction();
if (action == 2 && this.mIsBeingDragged) {
return true;
}
int y;
switch (action & 255) {
case 0:
y = (int) ev.getY();
if (!inChild((int) ev.getX(), y)) {
this.mIsBeingDragged = false;
recycleVelocityTracker();
break;
}
this.mLastMotionY = y;
this.mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
initOrResetVelocityTracker();
this.mVelocityTracker.addMovement(ev);
if (!this.mScroller.isFinished()) {
z = true;
}
this.mIsBeingDragged = z;
startNestedScroll(2);
break;
case 1:
case 3:
this.mIsBeingDragged = false;
this.mActivePointerId = -1;
recycleVelocityTracker();
if (this.mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) {
ViewCompat.postInvalidateOnAnimation(this);
}
stopNestedScroll();
break;
case 2:
int activePointerId = this.mActivePointerId;
if (activePointerId != -1) {
int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
if (pointerIndex != -1) {
y = (int) MotionEventCompat.getY(ev, pointerIndex);
if (Math.abs(y - this.mLastMotionY) > this.mTouchSlop && (getNestedScrollAxes() & 2) == 0) {
this.mIsBeingDragged = true;
this.mLastMotionY = y;
initVelocityTrackerIfNotExists();
this.mVelocityTracker.addMovement(ev);
this.mNestedYOffset = 0;
ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
break;
}
}
}
Log.e(TAG, "Invalid pointerId=" + activePointerId + " in onInterceptTouchEvent");
break;
}
break;
case 6:
onSecondaryPointerUp(ev);
break;
}
return this.mIsBeingDragged;
}
public boolean onTouchEvent(MotionEvent ev) {
initVelocityTrackerIfNotExists();
MotionEvent vtev = MotionEvent.obtain(ev);
int actionMasked = MotionEventCompat.getActionMasked(ev);
if (actionMasked == 0) {
this.mNestedYOffset = 0;
}
vtev.offsetLocation(0.0f, (float) this.mNestedYOffset);
ViewParent parent;
switch (actionMasked) {
case 0:
if (getChildCount() != 0) {
boolean z = !this.mScroller.isFinished();
this.mIsBeingDragged = z;
if (z) {
parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
if (!this.mScroller.isFinished()) {
this.mScroller.abortAnimation();
}
this.mLastMotionY = (int) ev.getY();
this.mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
startNestedScroll(2);
break;
}
return false;
case 1:
if (this.mIsBeingDragged) {
VelocityTracker velocityTracker = this.mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, (float) this.mMaximumVelocity);
int initialVelocity = (int) VelocityTrackerCompat.getYVelocity(velocityTracker, this.mActivePointerId);
if (Math.abs(initialVelocity) > this.mMinimumVelocity) {
flingWithNestedDispatch(-initialVelocity);
} else if (this.mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
this.mActivePointerId = -1;
endDrag();
break;
case 2:
int activePointerIndex = MotionEventCompat.findPointerIndex(ev, this.mActivePointerId);
if (activePointerIndex != -1) {
int y = (int) MotionEventCompat.getY(ev, activePointerIndex);
int deltaY = this.mLastMotionY - y;
if (dispatchNestedPreScroll(0, deltaY, this.mScrollConsumed, this.mScrollOffset)) {
deltaY -= this.mScrollConsumed[1];
vtev.offsetLocation(0.0f, (float) this.mScrollOffset[1]);
this.mNestedYOffset += this.mScrollOffset[1];
}
if (!this.mIsBeingDragged && Math.abs(deltaY) > this.mTouchSlop) {
parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
this.mIsBeingDragged = true;
if (deltaY > 0) {
deltaY -= this.mTouchSlop;
} else {
deltaY += this.mTouchSlop;
}
}
if (this.mIsBeingDragged) {
this.mLastMotionY = y - this.mScrollOffset[1];
int oldY = getScrollY();
int range = getScrollRange();
int overscrollMode = ViewCompat.getOverScrollMode(this);
boolean canOverscroll = overscrollMode == 0 || (overscrollMode == 1 && range > 0);
if (overScrollByCompat(0, deltaY, 0, getScrollY(), 0, range, 0, 0, true) && !hasNestedScrollingParent()) {
this.mVelocityTracker.clear();
}
int scrolledDeltaY = getScrollY() - oldY;
if (!dispatchNestedScroll(0, scrolledDeltaY, 0, deltaY - scrolledDeltaY, this.mScrollOffset)) {
if (canOverscroll) {
ensureGlows();
int pulledToY = oldY + deltaY;
if (pulledToY < 0) {
this.mEdgeGlowTop.onPull(((float) deltaY) / ((float) getHeight()), MotionEventCompat.getX(ev, activePointerIndex) / ((float) getWidth()));
if (!this.mEdgeGlowBottom.isFinished()) {
this.mEdgeGlowBottom.onRelease();
}
} else if (pulledToY > range) {
this.mEdgeGlowBottom.onPull(((float) deltaY) / ((float) getHeight()), 1.0f - (MotionEventCompat.getX(ev, activePointerIndex) / ((float) getWidth())));
if (!this.mEdgeGlowTop.isFinished()) {
this.mEdgeGlowTop.onRelease();
}
}
if (!(this.mEdgeGlowTop == null || (this.mEdgeGlowTop.isFinished() && this.mEdgeGlowBottom.isFinished()))) {
ViewCompat.postInvalidateOnAnimation(this);
break;
}
}
}
this.mLastMotionY -= this.mScrollOffset[1];
vtev.offsetLocation(0.0f, (float) this.mScrollOffset[1]);
this.mNestedYOffset += this.mScrollOffset[1];
break;
}
}
Log.e(TAG, "Invalid pointerId=" + this.mActivePointerId + " in onTouchEvent");
break;
break;
case 3:
if (this.mIsBeingDragged && getChildCount() > 0 && this.mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0, getScrollRange())) {
ViewCompat.postInvalidateOnAnimation(this);
}
this.mActivePointerId = -1;
endDrag();
break;
case 5:
int index = MotionEventCompat.getActionIndex(ev);
this.mLastMotionY = (int) MotionEventCompat.getY(ev, index);
this.mActivePointerId = MotionEventCompat.getPointerId(ev, index);
break;
case 6:
onSecondaryPointerUp(ev);
this.mLastMotionY = (int) MotionEventCompat.getY(ev, MotionEventCompat.findPointerIndex(ev, this.mActivePointerId));
break;
}
if (this.mVelocityTracker != null) {
this.mVelocityTracker.addMovement(vtev);
}
vtev.recycle();
return true;
}
private void onSecondaryPointerUp(MotionEvent ev) {
int pointerIndex = (ev.getAction() & MotionEventCompat.ACTION_POINTER_INDEX_MASK) >> 8;
if (MotionEventCompat.getPointerId(ev, pointerIndex) == this.mActivePointerId) {
int newPointerIndex = pointerIndex == 0 ? 1 : 0;
this.mLastMotionY = (int) MotionEventCompat.getY(ev, newPointerIndex);
this.mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
if (this.mVelocityTracker != null) {
this.mVelocityTracker.clear();
}
}
}
public boolean onGenericMotionEvent(MotionEvent event) {
if ((MotionEventCompat.getSource(event) & 2) != 0) {
switch (event.getAction()) {
case 8:
if (!this.mIsBeingDragged) {
float vscroll = MotionEventCompat.getAxisValue(event, 9);
if (vscroll != 0.0f) {
int delta = (int) (getVerticalScrollFactorCompat() * vscroll);
int range = getScrollRange();
int oldScrollY = getScrollY();
int newScrollY = oldScrollY - delta;
if (newScrollY < 0) {
newScrollY = 0;
} else if (newScrollY > range) {
newScrollY = range;
}
if (newScrollY != oldScrollY) {
super.scrollTo(getScrollX(), newScrollY);
return true;
}
}
}
break;
}
}
return false;
}
private float getVerticalScrollFactorCompat() {
if (this.mVerticalScrollFactor == 0.0f) {
TypedValue outValue = new TypedValue();
Context context = getContext();
if (context.getTheme().resolveAttribute(16842829, outValue, true)) {
this.mVerticalScrollFactor = outValue.getDimension(context.getResources().getDisplayMetrics());
} else {
throw new IllegalStateException("Expected theme to define listPreferredItemHeight.");
}
}
return this.mVerticalScrollFactor;
}
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
super.scrollTo(scrollX, scrollY);
}
boolean overScrollByCompat(int deltaX, int deltaY, int scrollX, int scrollY, int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY, boolean isTouchEvent) {
int overScrollMode = ViewCompat.getOverScrollMode(this);
boolean canScrollHorizontal = computeHorizontalScrollRange() > computeHorizontalScrollExtent();
boolean canScrollVertical = computeVerticalScrollRange() > computeVerticalScrollExtent();
boolean overScrollHorizontal = overScrollMode == 0 || (overScrollMode == 1 && canScrollHorizontal);
boolean overScrollVertical = overScrollMode == 0 || (overScrollMode == 1 && canScrollVertical);
int newScrollX = scrollX + deltaX;
if (!overScrollHorizontal) {
maxOverScrollX = 0;
}
int newScrollY = scrollY + deltaY;
if (!overScrollVertical) {
maxOverScrollY = 0;
}
int left = -maxOverScrollX;
int right = maxOverScrollX + scrollRangeX;
int top = -maxOverScrollY;
int bottom = maxOverScrollY + scrollRangeY;
boolean clampedX = false;
if (newScrollX > right) {
newScrollX = right;
clampedX = true;
} else if (newScrollX < left) {
newScrollX = left;
clampedX = true;
}
boolean clampedY = false;
if (newScrollY > bottom) {
newScrollY = bottom;
clampedY = true;
} else if (newScrollY < top) {
newScrollY = top;
clampedY = true;
}
if (clampedY) {
this.mScroller.springBack(newScrollX, newScrollY, 0, 0, 0, getScrollRange());
}
onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
if (clampedX || clampedY) {
return true;
}
return false;
}
private int getScrollRange() {
if (getChildCount() > 0) {
return Math.max(0, getChildAt(0).getHeight() - ((getHeight() - getPaddingBottom()) - getPaddingTop()));
}
return 0;
}
private View findFocusableViewInBounds(boolean topFocus, int top, int bottom) {
List<View> focusables = getFocusables(2);
View focusCandidate = null;
boolean foundFullyContainedFocusable = false;
int count = focusables.size();
for (int i = 0; i < count; i++) {
View view = (View) focusables.get(i);
int viewTop = view.getTop();
int viewBottom = view.getBottom();
if (top < viewBottom && viewTop < bottom) {
boolean viewIsFullyContained = top < viewTop && viewBottom < bottom;
if (focusCandidate == null) {
focusCandidate = view;
foundFullyContainedFocusable = viewIsFullyContained;
} else {
boolean viewIsCloserToBoundary = (topFocus && viewTop < focusCandidate.getTop()) || (!topFocus && viewBottom > focusCandidate.getBottom());
if (foundFullyContainedFocusable) {
if (viewIsFullyContained && viewIsCloserToBoundary) {
focusCandidate = view;
}
} else if (viewIsFullyContained) {
focusCandidate = view;
foundFullyContainedFocusable = true;
} else if (viewIsCloserToBoundary) {
focusCandidate = view;
}
}
}
}
return focusCandidate;
}
public boolean pageScroll(int direction) {
boolean down;
if (direction == TransportMediator.KEYCODE_MEDIA_RECORD) {
down = true;
} else {
down = false;
}
int height = getHeight();
if (down) {
this.mTempRect.top = getScrollY() + height;
int count = getChildCount();
if (count > 0) {
View view = getChildAt(count - 1);
if (this.mTempRect.top + height > view.getBottom()) {
this.mTempRect.top = view.getBottom() - height;
}
}
} else {
this.mTempRect.top = getScrollY() - height;
if (this.mTempRect.top < 0) {
this.mTempRect.top = 0;
}
}
this.mTempRect.bottom = this.mTempRect.top + height;
return scrollAndFocus(direction, this.mTempRect.top, this.mTempRect.bottom);
}
public boolean fullScroll(int direction) {
boolean down;
if (direction == TransportMediator.KEYCODE_MEDIA_RECORD) {
down = true;
} else {
down = false;
}
int height = getHeight();
this.mTempRect.top = 0;
this.mTempRect.bottom = height;
if (down) {
int count = getChildCount();
if (count > 0) {
View view = getChildAt(count - 1);
this.mTempRect.bottom = view.getBottom() + getPaddingBottom();
this.mTempRect.top = this.mTempRect.bottom - height;
}
}
return scrollAndFocus(direction, this.mTempRect.top, this.mTempRect.bottom);
}
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 == 33;
View newFocused = findFocusableViewInBounds(up, top, bottom);
if (newFocused == null) {
newFocused = this;
}
if (top < containerTop || bottom > containerBottom) {
doScrollY(up ? top - containerTop : bottom - containerBottom);
} else {
handled = false;
}
if (newFocused != findFocus()) {
newFocused.requestFocus(direction);
}
return handled;
}
public boolean arrowScroll(int direction) {
View currentFocused = findFocus();
if (currentFocused == this) {
currentFocused = null;
}
View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction);
int maxJump = getMaxScrollAmount();
if (nextFocused == null || !isWithinDeltaOfScreen(nextFocused, maxJump, getHeight())) {
int scrollDelta = maxJump;
if (direction == 33 && getScrollY() < scrollDelta) {
scrollDelta = getScrollY();
} else if (direction == TransportMediator.KEYCODE_MEDIA_RECORD && getChildCount() > 0) {
int daBottom = getChildAt(0).getBottom();
int screenBottom = (getScrollY() + getHeight()) - getPaddingBottom();
if (daBottom - screenBottom < maxJump) {
scrollDelta = daBottom - screenBottom;
}
}
if (scrollDelta == 0) {
return false;
}
int i;
if (direction == TransportMediator.KEYCODE_MEDIA_RECORD) {
i = scrollDelta;
} else {
i = -scrollDelta;
}
doScrollY(i);
} else {
nextFocused.getDrawingRect(this.mTempRect);
offsetDescendantRectToMyCoords(nextFocused, this.mTempRect);
doScrollY(computeScrollDeltaToGetChildRectOnScreen(this.mTempRect));
nextFocused.requestFocus(direction);
}
if (currentFocused != null && currentFocused.isFocused() && isOffScreen(currentFocused)) {
int descendantFocusability = getDescendantFocusability();
setDescendantFocusability(131072);
requestFocus();
setDescendantFocusability(descendantFocusability);
}
return true;
}
private boolean isOffScreen(View descendant) {
return !isWithinDeltaOfScreen(descendant, 0, getHeight());
}
private boolean isWithinDeltaOfScreen(View descendant, int delta, int height) {
descendant.getDrawingRect(this.mTempRect);
offsetDescendantRectToMyCoords(descendant, this.mTempRect);
return this.mTempRect.bottom + delta >= getScrollY() && this.mTempRect.top - delta <= getScrollY() + height;
}
private void doScrollY(int delta) {
if (delta == 0) {
return;
}
if (this.mSmoothScrollingEnabled) {
smoothScrollBy(0, delta);
} else {
scrollBy(0, delta);
}
}
public final void smoothScrollBy(int dx, int dy) {
if (getChildCount() != 0) {
if (AnimationUtils.currentAnimationTimeMillis() - this.mLastScroll > 250) {
int maxY = Math.max(0, getChildAt(0).getHeight() - ((getHeight() - getPaddingBottom()) - getPaddingTop()));
int scrollY = getScrollY();
this.mScroller.startScroll(getScrollX(), scrollY, 0, Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY);
ViewCompat.postInvalidateOnAnimation(this);
} else {
if (!this.mScroller.isFinished()) {
this.mScroller.abortAnimation();
}
scrollBy(dx, dy);
}
this.mLastScroll = AnimationUtils.currentAnimationTimeMillis();
}
}
public final void smoothScrollTo(int x, int y) {
smoothScrollBy(x - getScrollX(), y - getScrollY());
}
public int computeVerticalScrollRange() {
int contentHeight = (getHeight() - getPaddingBottom()) - getPaddingTop();
if (getChildCount() == 0) {
return contentHeight;
}
int scrollRange = getChildAt(0).getBottom();
int scrollY = getScrollY();
int overscrollBottom = Math.max(0, scrollRange - contentHeight);
if (scrollY < 0) {
scrollRange -= scrollY;
} else if (scrollY > overscrollBottom) {
scrollRange += scrollY - overscrollBottom;
}
return scrollRange;
}
public int computeVerticalScrollOffset() {
return Math.max(0, super.computeVerticalScrollOffset());
}
public int computeVerticalScrollExtent() {
return super.computeVerticalScrollExtent();
}
public int computeHorizontalScrollRange() {
return super.computeHorizontalScrollRange();
}
public int computeHorizontalScrollOffset() {
return super.computeHorizontalScrollOffset();
}
public int computeHorizontalScrollExtent() {
return super.computeHorizontalScrollExtent();
}
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) {
child.measure(getChildMeasureSpec(parentWidthMeasureSpec, getPaddingLeft() + getPaddingRight(), child.getLayoutParams().width), MeasureSpec.makeMeasureSpec(0, 0));
}
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
child.measure(getChildMeasureSpec(parentWidthMeasureSpec, (((getPaddingLeft() + getPaddingRight()) + lp.leftMargin) + lp.rightMargin) + widthUsed, lp.width), MeasureSpec.makeMeasureSpec(lp.topMargin + lp.bottomMargin, 0));
}
public void computeScroll() {
boolean canOverscroll = true;
if (this.mScroller.computeScrollOffset()) {
int oldX = getScrollX();
int oldY = getScrollY();
int x = this.mScroller.getCurrX();
int y = this.mScroller.getCurrY();
if (oldX != x || oldY != y) {
int range = getScrollRange();
int overscrollMode = ViewCompat.getOverScrollMode(this);
if (overscrollMode != 0 && (overscrollMode != 1 || range <= 0)) {
canOverscroll = false;
}
overScrollByCompat(x - oldX, y - oldY, oldX, oldY, 0, range, 0, 0, false);
if (canOverscroll) {
ensureGlows();
if (y <= 0 && oldY > 0) {
this.mEdgeGlowTop.onAbsorb((int) this.mScroller.getCurrVelocity());
} else if (y >= range && oldY < range) {
this.mEdgeGlowBottom.onAbsorb((int) this.mScroller.getCurrVelocity());
}
}
}
}
}
private void scrollToChild(View child) {
child.getDrawingRect(this.mTempRect);
offsetDescendantRectToMyCoords(child, this.mTempRect);
int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(this.mTempRect);
if (scrollDelta != 0) {
scrollBy(0, scrollDelta);
}
}
private boolean scrollToChildRect(Rect rect, boolean immediate) {
boolean scroll;
int delta = computeScrollDeltaToGetChildRectOnScreen(rect);
if (delta != 0) {
scroll = true;
} else {
scroll = false;
}
if (scroll) {
if (immediate) {
scrollBy(0, delta);
} else {
smoothScrollBy(0, delta);
}
}
return scroll;
}
protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) {
if (getChildCount() == 0) {
return 0;
}
int height = getHeight();
int screenTop = getScrollY();
int screenBottom = screenTop + height;
int fadingEdge = getVerticalFadingEdgeLength();
if (rect.top > 0) {
screenTop += fadingEdge;
}
if (rect.bottom < getChildAt(0).getHeight()) {
screenBottom -= fadingEdge;
}
int scrollYDelta;
if (rect.bottom > screenBottom && rect.top > screenTop) {
if (rect.height() > height) {
scrollYDelta = 0 + (rect.top - screenTop);
} else {
scrollYDelta = 0 + (rect.bottom - screenBottom);
}
return Math.min(scrollYDelta, getChildAt(0).getBottom() - screenBottom);
} else if (rect.top >= screenTop || rect.bottom >= screenBottom) {
return 0;
} else {
if (rect.height() > height) {
scrollYDelta = 0 - (screenBottom - rect.bottom);
} else {
scrollYDelta = 0 - (screenTop - rect.top);
}
return Math.max(scrollYDelta, -getScrollY());
}
}
public void requestChildFocus(View child, View focused) {
if (this.mIsLayoutDirty) {
this.mChildToScrollTo = focused;
} else {
scrollToChild(focused);
}
super.requestChildFocus(child, focused);
}
protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
if (direction == 2) {
direction = TransportMediator.KEYCODE_MEDIA_RECORD;
} else if (direction == 1) {
direction = 33;
}
View nextFocus = previouslyFocusedRect == null ? FocusFinder.getInstance().findNextFocus(this, null, direction) : FocusFinder.getInstance().findNextFocusFromRect(this, previouslyFocusedRect, direction);
if (nextFocus == null || isOffScreen(nextFocus)) {
return false;
}
return nextFocus.requestFocus(direction, previouslyFocusedRect);
}
public boolean requestChildRectangleOnScreen(View child, Rect rectangle, boolean immediate) {
rectangle.offset(child.getLeft() - child.getScrollX(), child.getTop() - child.getScrollY());
return scrollToChildRect(rectangle, immediate);
}
public void requestLayout() {
this.mIsLayoutDirty = true;
super.requestLayout();
}
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
this.mIsLayoutDirty = false;
if (this.mChildToScrollTo != null && isViewDescendantOf(this.mChildToScrollTo, this)) {
scrollToChild(this.mChildToScrollTo);
}
this.mChildToScrollTo = null;
if (!this.mIsLaidOut) {
if (this.mSavedState != null) {
scrollTo(getScrollX(), this.mSavedState.scrollPosition);
this.mSavedState = null;
}
int scrollRange = Math.max(0, (getChildCount() > 0 ? getChildAt(0).getMeasuredHeight() : 0) - (((b - t) - getPaddingBottom()) - getPaddingTop()));
if (getScrollY() > scrollRange) {
scrollTo(getScrollX(), scrollRange);
} else if (getScrollY() < 0) {
scrollTo(getScrollX(), 0);
}
}
scrollTo(getScrollX(), getScrollY());
this.mIsLaidOut = true;
}
public void onAttachedToWindow() {
this.mIsLaidOut = false;
}
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
View currentFocused = findFocus();
if (currentFocused != null && this != currentFocused && isWithinDeltaOfScreen(currentFocused, 0, oldh)) {
currentFocused.getDrawingRect(this.mTempRect);
offsetDescendantRectToMyCoords(currentFocused, this.mTempRect);
doScrollY(computeScrollDeltaToGetChildRectOnScreen(this.mTempRect));
}
}
private static boolean isViewDescendantOf(View child, View parent) {
if (child == parent) {
return true;
}
ViewParent theParent = child.getParent();
if ((theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent)) {
return true;
}
return false;
}
public void fling(int velocityY) {
if (getChildCount() > 0) {
int height = (getHeight() - getPaddingBottom()) - getPaddingTop();
int i = velocityY;
int i2 = 0;
int i3 = 0;
int i4 = 0;
int i5 = 0;
this.mScroller.fling(getScrollX(), getScrollY(), 0, i, i2, i3, i4, Math.max(0, getChildAt(0).getHeight() - height), i5, height / 2);
ViewCompat.postInvalidateOnAnimation(this);
}
}
private void flingWithNestedDispatch(int velocityY) {
int scrollY = getScrollY();
boolean canFling = (scrollY > 0 || velocityY > 0) && (scrollY < getScrollRange() || velocityY < 0);
if (!dispatchNestedPreFling(0.0f, (float) velocityY)) {
dispatchNestedFling(0.0f, (float) velocityY, canFling);
if (canFling) {
fling(velocityY);
}
}
}
private void endDrag() {
this.mIsBeingDragged = false;
recycleVelocityTracker();
stopNestedScroll();
if (this.mEdgeGlowTop != null) {
this.mEdgeGlowTop.onRelease();
this.mEdgeGlowBottom.onRelease();
}
}
public void scrollTo(int x, int y) {
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);
}
}
}
private void ensureGlows() {
if (ViewCompat.getOverScrollMode(this) == 2) {
this.mEdgeGlowTop = null;
this.mEdgeGlowBottom = null;
} else if (this.mEdgeGlowTop == null) {
Context context = getContext();
this.mEdgeGlowTop = new EdgeEffectCompat(context);
this.mEdgeGlowBottom = new EdgeEffectCompat(context);
}
}
public void draw(Canvas canvas) {
super.draw(canvas);
if (this.mEdgeGlowTop != null) {
int restoreCount;
int width;
int scrollY = getScrollY();
if (!this.mEdgeGlowTop.isFinished()) {
restoreCount = canvas.save();
width = (getWidth() - getPaddingLeft()) - getPaddingRight();
canvas.translate((float) getPaddingLeft(), (float) Math.min(0, scrollY));
this.mEdgeGlowTop.setSize(width, getHeight());
if (this.mEdgeGlowTop.draw(canvas)) {
ViewCompat.postInvalidateOnAnimation(this);
}
canvas.restoreToCount(restoreCount);
}
if (!this.mEdgeGlowBottom.isFinished()) {
restoreCount = canvas.save();
width = (getWidth() - getPaddingLeft()) - getPaddingRight();
int height = getHeight();
canvas.translate((float) ((-width) + getPaddingLeft()), (float) (Math.max(getScrollRange(), scrollY) + height));
canvas.rotate(180.0f, (float) width, 0.0f);
this.mEdgeGlowBottom.setSize(width, height);
if (this.mEdgeGlowBottom.draw(canvas)) {
ViewCompat.postInvalidateOnAnimation(this);
}
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;
}
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
this.mSavedState = ss;
requestLayout();
}
protected Parcelable onSaveInstanceState() {
SavedState ss = new SavedState(super.onSaveInstanceState());
ss.scrollPosition = getScrollY();
return ss;
}
}