android 上下左右都能滑动的是类似scorllview效果

/**

  • @return The maximum amount this scroll view will scroll in response to an

  •     arrow event.
    

*/

public int getMaxScrollAmountV() {

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

}

public int getMaxScrollAmountH() {

return (int) (MAX_SCROLL_FACTOR * (getRight() - getLeft()));

}

private void initScrollView() {

mScroller = new Scroller(getContext());

setFocusable(true);

setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);

setWillNotDraw(false);

setFillViewport(true);//子布局fill有效,宽高可以设置为最大

final ViewConfiguration configuration = ViewConfiguration

.get(getContext());

mTouchSlop = configuration.getScaledTouchSlop();

mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();

mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();

}

@Override

public void addView(View child) {

if (getChildCount() > 0) {

throw new IllegalStateException(

“ScrollView can host only one direct child”);

}

super.addView(child);

}

@Override

public void addView(View child, int index) {

if (getChildCount() > 0) {

throw new IllegalStateException(

“ScrollView can host only one direct child”);

}

super.addView(child, index);

}

@Override

public void addView(View child, ViewGroup.LayoutParams params) {

if (getChildCount() > 0) {

throw new IllegalStateException(

“ScrollView can host only one direct child”);

}

super.addView(child, params);

}

@Override

public void addView(View child, int index, ViewGroup.LayoutParams params) {

if (getChildCount() > 0) {

throw new IllegalStateException(

“ScrollView can host only one direct child”);

}

super.addView(child, index, params);

}

/**

  • @return Returns true this ScrollView can be scrolled

*/

private boolean canScrollV() {

View child = getChildAt(0);

if (child != null) {

int childHeight = child.getHeight();

return getHeight() < childHeight + getPaddingTop()

  • getPaddingBottom();

}

return false;

}

private boolean canScrollH() {

View child = getChildAt(0);

if (child != null) {

int childWidth = child.getWidth();

return getWidth() < childWidth + getPaddingLeft()

  • getPaddingRight();

}

return false;

}

/**

  • Indicates whether this ScrollView’s content is stretched to fill the

  • viewport.

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

*/

public boolean isFillViewport() {

return mFillViewport;

}

/**

  • Indicates this ScrollView whether it should stretch its content height to

  • fill the viewport or not.

  • @param fillViewport

  •        True to stretch the content's height to the viewport's
    
  •        boundaries, false otherwise.
    

*/

public void setFillViewport(boolean fillViewport) {

if (fillViewport != mFillViewport) {

mFillViewport = fillViewport;

requestLayout();

}

}

/**

  • @return Whether arrow scrolling will animate its transition.

*/

public boolean isSmoothScrollingEnabled() {

return mSmoothScrollingEnabled;

}

/**

  • Set whether arrow scrolling will animate its transition.

  • @param smoothScrollingEnabled

  •        whether arrow scrolling will animate its transition
    

*/

public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) {

mSmoothScrollingEnabled = smoothScrollingEnabled;

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec, heightMeasureSpec);

if (!mFillViewport) {

return;

}

final int heightMode = MeasureSpec.getMode(heightMeasureSpec);

final int widthMode = MeasureSpec.getMode(widthMeasureSpec);

if (heightMode == MeasureSpec.UNSPECIFIED

&& widthMode == MeasureSpec.UNSPECIFIED) {

return;

}

if (getChildCount() > 0) {

final View child = getChildAt(0);

int height = getMeasuredHeight();

int width = getMeasuredWidth();

if (child.getMeasuredHeight() < height

|| child.getMeasuredWidth() < width) {

width -= getPaddingLeft();

width -= getPaddingRight();

int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,

MeasureSpec.EXACTLY);

height -= getPaddingTop();

height -= getPaddingBottom();

int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(

height, MeasureSpec.EXACTLY);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

}

}

@Override

public boolean dispatchKeyEvent(KeyEvent event) {

// Let the focused view and/or our descendants get the key first

return super.dispatchKeyEvent(event) || executeKeyEvent(event);

}

/**

  • You can call this function yourself to have the scroll view perform

  • scrolling from a key event, just as if the event had been dispatched to

  • it by the view hierarchy.

  • @param event

  •        The key event to execute.
    
  • @return Return true if the event was handled, else false.

*/

public boolean executeKeyEvent(KeyEvent event) {

mTempRect.setEmpty();

boolean handled = false;

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

switch (event.getKeyCode()) {

case KeyEvent.KEYCODE_DPAD_LEFT:

if (canScrollH()) {

if (!event.isAltPressed()) {

handled = arrowScrollH(View.FOCUS_LEFT);

} else {

handled = fullScrollH(View.FOCUS_LEFT);

}

}

break;

case KeyEvent.KEYCODE_DPAD_RIGHT:

if (canScrollH()) {

if (!event.isAltPressed()) {

handled = arrowScrollH(View.FOCUS_RIGHT);

} else {

handled = fullScrollH(View.FOCUS_RIGHT);

}

}

break;

case KeyEvent.KEYCODE_DPAD_UP:

if (canScrollV()) {

if (!event.isAltPressed()) {

handled = arrowScrollV(View.FOCUS_UP);

} else {

handled = fullScrollV(View.FOCUS_UP);

}

}

break;

case KeyEvent.KEYCODE_DPAD_DOWN:

if (canScrollV()) {

if (!event.isAltPressed()) {

handled = arrowScrollV(View.FOCUS_DOWN);

} else {

handled = fullScrollV(View.FOCUS_DOWN);

}

}

break;

}

}

return handled;

}

private boolean inChild(int x, int y) {

if (getChildCount() > 0) {

final int scrollX = getScrollX();

final int scrollY = getScrollY();

final View child = getChildAt(0);

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

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

|| 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. mLastMotionY is set to the y 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 y = ev.getY(pointerIndex);

final int yDiff = (int) Math.abs(y - mLastMotionY);

if (yDiff > mTouchSlop) {

mIsBeingDragged = true;

mLastMotionY = y;

}

final float x = ev.getX(pointerIndex);

final int xDiff = (int) Math.abs(x - mLastMotionX);

if (xDiff > mTouchSlop) {

mIsBeingDragged = true;

mLastMotionX = x;

}

break;

}

case MotionEvent.ACTION_DOWN: {

final float x = ev.getX();

final float y = ev.getY();

if (!inChild((int) x, (int) y)) {

mIsBeingDragged = false;

break;

}

/*

  • Remember location of down touch. ACTION_DOWN always refers to

  • pointer index 0.

*/

mLastMotionY = y;

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;

}

private boolean scrollableOutsideTouch = false;

/**

  • 设置scroller是否可以滑动内容当触屏事件在chileview之外 default:false

  • @param b

  •        true-可以,false-不可
    

*/

public void setScrollableOutsideChile(boolean b) {

scrollableOutsideTouch = b;

}

private boolean flexible = true;

/**

  • 设置是否可有弹性效果

  • @param b

  •        true-可以,false-不可
    

*/

public void setFlexible(boolean b) {

flexible = b;

}

private long lastEvenTime;

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

final float y = ev.getY();

if (!(mIsBeingDragged = inChild((int) x, (int) y))

&& !scrollableOutsideTouch) {

return false;

}

// 阻止测试人员暴力测试

if (System.currentTimeMillis() - lastEvenTime < 200) {

ev.setAction(MotionEvent.ACTION_CANCEL);

}

lastEvenTime = System.currentTimeMillis();

/*

  • 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

mLastMotionY = y;

mLastMotionX = x;

mActivePointerId = ev.getPointerId(0);

break;

}

case MotionEvent.ACTION_MOVE:

if (mIsBeingDragged || scrollableOutsideTouch) {

// Scroll to follow the motion event

final int activePointerIndex = ev

.findPointerIndex(mActivePointerId);

final float y = ev.getY(activePointerIndex);

final int deltaY = (int) (mLastMotionY - y);//恢复纵向滚动

// final int deltaY = 0;//禁止纵向滚动

mLastMotionY = y;

final float x = ev.getX(activePointerIndex);

// final int deltaX = (int) (mLastMotionX - x);//恢复横向滚动

final int deltaX = 0;//禁止横向滚动

mLastMotionX = x;

// 全方向滚动

scrollBy(deltaX, deltaY);

// 当滚动到边界时就不会再滚动,这时移动布局

if (isNeedMove() && flexible) {

if (normal.isEmpty()) {

// 保存正常的布局属性

normal.set(inner.getLeft(), inner.getTop(),

inner.getRight(), inner.getBottom());

}

// 移动布局

inner.layout(inner.getLeft() - deltaX / 2, inner.getTop()

  • deltaY / 2, inner.getRight() - deltaX / 2,

inner.getBottom() - deltaY / 2);

}

}

break;

case MotionEvent.ACTION_UP:

if (mIsBeingDragged || scrollableOutsideTouch) {

if (mFlingEnabled) {

final VelocityTracker velocityTracker = mVelocityTracker;

velocityTracker.computeCurrentVelocity(1000,

mMaximumVelocity);

int initialVelocitx = (int) velocityTracker

.getXVelocity(mActivePointerId);

int initialVelocity = (int) velocityTracker

.getYVelocity(mActivePointerId);

if (getChildCount() > 0) {

if (Math.abs(initialVelocitx) > initialVelocitx

|| Math.abs(initialVelocity) > mMinimumVelocity) {

fling(-initialVelocitx, -initialVelocity);

}

}

}

if (isNeedAnimation()) {

animation();

}

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.

final int newPointerIndex = pointerIndex == 0 ? 1 : 0;

mLastMotionX = ev.getX(newPointerIndex);

mLastMotionY = ev.getY(newPointerIndex);

mActivePointerId = ev.getPointerId(newPointerIndex);

if (mVelocityTracker != null) {

mVelocityTracker.clear();

}

}

}

/**

  • 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 findFocusableViewInBoundsV(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;

}

private View findFocusableViewInBoundsH(boolean leftFocus, int left,

int right) {

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

}

/**

  • 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 fullScrollV(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();

mTempRect.top = mTempRect.bottom - height;

}

}

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

}

public boolean fullScrollH(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 scrollAndFocusH(direction, mTempRect.left, mTempRect.right);

}

/**

  • 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 scrollAndFocusV(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 = findFocusableViewInBoundsV(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)) {

mScrollViewMovedFocus = true;

mScrollViewMovedFocus = false;

}

return handled;

}

private boolean scrollAndFocusH(int direction, int left, int right) {

boolean handled = true;

int width = getWidth();

int containerLeft = getScrollX();

int containerRight = containerLeft + width;

boolean goLeft = direction == View.FOCUS_LEFT;

View newFocused = findFocusableViewInBoundsH(goLeft, left, right);

if (newFocused == null) {

newFocused = this;

}

if (left >= containerLeft && right <= containerRight) {

handled = false;

} else {

int delta = goLeft ? (left - containerLeft)
(right - containerRight);

doScrollX(delta);

}

if (newFocused != findFocus() && newFocused.requestFocus(direction)) {

mScrollViewMovedFocus = true;

mScrollViewMovedFocus = false;

}

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

*/

public boolean arrowScrollV(int direction) {

View currentFocused = findFocus();

if (currentFocused == this)

currentFocused = null;

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

currentFocused, direction);

final int maxJump = getMaxScrollAmountV();

if (nextFocused != null

&& isWithinDeltaOfScreenV(nextFocused, maxJump, getHeight())) {

nextFocused.getDrawingRect(mTempRect);

offsetDescendantRectToMyCoords(nextFocused, mTempRect);

int scrollDelta = computeScrollDeltaToGetChildRectOnScreenV(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();

if (daBottom - screenBottom < maxJump) {

scrollDelta = daBottom - screenBottom;

}

}

}

if (scrollDelta == 0) {

return false;

}

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

}

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

&& isOffScreenV(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;

}

public boolean arrowScrollH(int direction) {

View currentFocused = findFocus();

if (currentFocused == this)

currentFocused = null;

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

currentFocused, direction);

final int maxJump = getMaxScrollAmountH();

if (nextFocused != null && isWithinDeltaOfScreenH(nextFocused, maxJump)) {

nextFocused.getDrawingRect(mTempRect);

offsetDescendantRectToMyCoords(nextFocused, mTempRect);

int scrollDelta = computeScrollDeltaToGetChildRectOnScreenH(mTempRect);

doScrollX(scrollDelta);

nextFocused.requestFocus(direction);

} else {

// no new focus

int scrollDelta = maxJump;

if (direction == View.FOCUS_LEFT && getScrollX() < scrollDelta) {

scrollDelta = getScrollX();

} else if (direction == View.FOCUS_RIGHT && getChildCount() > 0) {

int daRight = getChildAt(0).getRight();

int screenRight = getScrollX() + getWidth();

if (daRight - screenRight < maxJump) {

scrollDelta = daRight - screenRight;

}

}

if (scrollDelta == 0) {

return false;

}

doScrollX(direction == View.FOCUS_RIGHT ? scrollDelta
-scrollDelta);

}

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

&& isOffScreenH(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.
    

*/

private boolean isOffScreenV(View descendant) {

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

}

private boolean isOffScreenH(View descendant) {

return !isWithinDeltaOfScreenH(descendant, 0);

}

/**

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

  •     of being on the screen.
    

*/

private boolean isWithinDeltaOfScreenV(View descendant, int delta,

int height) {

descendant.getDrawingRect(mTempRect);

offsetDescendantRectToMyCoords(descendant, mTempRect);

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

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

}

private boolean isWithinDeltaOfScreenH(View descendant, int delta) {

descendant.getDrawingRect(mTempRect);

offsetDescendantRectToMyCoords(descendant, mTempRect);

return (mTempRect.right + delta) >= getScrollX()

&& (mTempRect.left - delta) <= (getScrollX() + getWidth());

}

/**

  • Smooth scroll by a Y delta

  • @param delta

  •        the number of pixels to scroll by on the Y axis
    

*/

private void doScrollY(int delta) {

if (delta != 0) {

if (mSmoothScrollingEnabled) {

smoothScrollBy(0, delta);

} else {

scrollBy(0, delta);

}

}

}

private void doScrollX(int delta) {

if (delta != 0) {

if (mSmoothScrollingEnabled) {

smoothScrollBy(delta, 0);

} else {

scrollBy(delta, 0);

}

}

}

/**

  • 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 void smoothScrollBy(int dx, int dy) {

if (getChildCount() == 0) {

// Nothing to do.

return;

}

long duration = AnimationUtils.currentAnimationTimeMillis()

  • mLastScroll;

if (duration > ANIMATED_SCROLL_GAP) {

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;

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

final int right = getChildAt(0).getWidth();

final int maxX = Math.max(0, right - width);

final int scrollX = getScrollX();

dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX;

mScroller.startScroll(scrollX, scrollY, dx, dy);

invalidate();

} else {

if (!mScroller.isFinished()) {

mScroller.abortAnimation();

}

scrollBy(dx, dy);

}

mLastScroll = AnimationUtils.currentAnimationTimeMillis();

}

/**

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

}

return getChildAt(0).getBottom();

}

@Override

protected int computeHorizontalScrollRange() {

final int count = getChildCount();

final int contentWidth = getWidth() - getPaddingLeft()

  • getPaddingRight();

if (count == 0) {

return contentWidth;

}

return getChildAt(0).getRight();

}

@Override

protected int computeVerticalScrollOffset() {

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

}

@Override

protected int computeHorizontalScrollOffset() {

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

}

@Override

protected void measureChild(View child, int parentWidthMeasureSpec,

int parentHeightMeasureSpec) {

int childWidthMeasureSpec;

int childHeightMeasureSpec;

childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0,

MeasureSpec.UNSPECIFIED);

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 = MeasureSpec.makeMeasureSpec(

lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED);

final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(

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

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

}

@Override

public void computeScroll() {

if (mScroller.computeScrollOffset()) {

// This is called at drawing time by ViewGroup. We don’t want to

// re-show the scrollbars at this point, which scrollTo will do,

// so we replicate most of scrollTo here.

//

// It’s a little odd to call onScrollChanged from inside the

// drawing.

//

// It is, except when you remember that computeScroll() is used to

// animate scrolling. So unless we want to defer the

// onScrollChanged()

// until the end of the animated scrolling, we don’t really have a

// choice here.

//

// I agree. The alternative, which I think would be worse, is to

// post

// something and tell the subclasses later. This is bad because

// there

// will be a window where mScrollX/Y is different from what the app

// thinks it is.

//

int x = mScroller.getCurrX();

int y = mScroller.getCurrY();

if (getChildCount() > 0) {

View child = getChildAt(0);

x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(),

child.getWidth());

y = clamp(y,

getHeight() - getPaddingBottom() - getPaddingTop(),

child.getHeight());

super.scrollTo(x, y);

// getHeight()- child.getHeight()=y ->底部 y=0 ->顶端

// 惯性强度 mScroller.getDuration()

}

awakenScrollBars();

// Keep on drawing until the animation has finished.

postInvalidate();

}

}

/**

  • 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 scrollDeltaV = computeScrollDeltaToGetChildRectOnScreenV(mTempRect);

int scrollDeltaH = computeScrollDeltaToGetChildRectOnScreenH(mTempRect);

if (scrollDeltaH != 0 || scrollDeltaV != 0) {

scrollBy(scrollDeltaH, scrollDeltaV);

}

}

/**

  • 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 deltaV = computeScrollDeltaToGetChildRectOnScreenV(rect);

final int deltaH = computeScrollDeltaToGetChildRectOnScreenH(rect);

final boolean scroll = deltaH != 0 || deltaV != 0;

if (scroll) {

if (immediate) {

scrollBy(deltaH, deltaV);

} else {

smoothScrollBy(deltaH, deltaV);

}

}

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 computeScrollDeltaToGetChildRectOnScreenV(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

最后

由于题目很多整理答案的工作量太大,所以仅限于提供知识点,详细的很多问题和参考答案我都整理成了 PDF文件


《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
endantRectToMyCoords(child, mTempRect);

int scrollDeltaV = computeScrollDeltaToGetChildRectOnScreenV(mTempRect);

int scrollDeltaH = computeScrollDeltaToGetChildRectOnScreenH(mTempRect);

if (scrollDeltaH != 0 || scrollDeltaV != 0) {

scrollBy(scrollDeltaH, scrollDeltaV);

}

}

/**

  • 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 deltaV = computeScrollDeltaToGetChildRectOnScreenV(rect);

final int deltaH = computeScrollDeltaToGetChildRectOnScreenH(rect);

final boolean scroll = deltaH != 0 || deltaV != 0;

if (scroll) {

if (immediate) {

scrollBy(deltaH, deltaV);

} else {

smoothScrollBy(deltaH, deltaV);

}

}

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 computeScrollDeltaToGetChildRectOnScreenV(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

最后

由于题目很多整理答案的工作量太大,所以仅限于提供知识点,详细的很多问题和参考答案我都整理成了 PDF文件

[外链图片转存中…(img-6Ka5G2wH-1714406131049)]

[外链图片转存中…(img-oTgKBEfR-1714406131052)]

[外链图片转存中…(img-vWCpE6kA-1714406131054)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 29
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值