Android?ScrollView實現(xiàn)滾動超過邊界松手回彈
ScrollView滾動超過邊界,松手回彈
Android原生的ScrollView滑動到邊界之后,就不能再滑動了,感覺很生硬。不及再多滑動一段距離,松手后回彈這種效果順滑一些。
先查看下滾動里面代碼的處理
case MotionEvent.ACTION_MOVE: ? final int activePointerIndex = ev.findPointerIndex(mActivePointerId); ? if (activePointerIndex == -1) { ? ? ? ? ? ? ? ? ? ? Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); ? ? ? ? ? ? ? ? ? ? break; ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? final int y = (int) ev.getY(activePointerIndex); ? ? ? ? ? ? ? ? int deltaY = mLastMotionY - y; ? ? ? ? ? ? ? ? ……………………………… ? ? ? ? ? ? ? ? if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) { ? ? ? ? ? ? ? ? ? ? final ViewParent parent = getParent(); ? ? ? ? ? ? ? ? ? ? if (parent != null) { ? ? ? ? ? ? ? ? ? ? ? ? parent.requestDisallowInterceptTouchEvent(true); ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? mIsBeingDragged = true; ? ? ? ? ? ? ? ? ? ? if (deltaY > 0) { ? ? ? ? ? ? ? ? ? ? ? ? deltaY -= mTouchSlop; ? ? ? ? ? ? ? ? ? ? } else { ? ? ? ? ? ? ? ? ? ? ? ? deltaY += mTouchSlop; ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? if (mIsBeingDragged) { ? ? ? ? ? ? ? ? ? ? // Scroll to follow the motion event ? ? ? ? ? ? ? ? ? ? mLastMotionY = y - mScrollOffset[1]; ? ? ? ? ? ? ? ? ? ? final int oldY = mScrollY; ? ? ? ? ? ? ? ? ? ? final int range = getScrollRange(); ? ? ? ? ? ? ? ? ? ? final int overscrollMode = getOverScrollMode(); ? ? ? ? ? ? ? ? ? ? boolean canOverscroll = overscrollMode == OVER_SCROLL_ALWAYS || ? ? ? ? ? ? ? ? ? ? ? ? ? ? (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); ? ? ? ? ? ? ? ? ? ? // Calling overScrollBy will call onOverScrolled, which ? ? ? ? ? ? ? ? ? ? // calls onScrollChanged if applicable. ? ? ? ? ? ? ? ? ? ? if (overScrollBy(0, deltaY, 0, mScrollY, 0, range, 0, mOverscrollDistance, true) ? ? ? ? ? ? ? ? ? ? ? ? ? ? && !hasNestedScrollingParent()) { ? ? ? ? ? ? ? ? ? ? ? ? // Break our velocity if we hit a scroll barrier. ? ? ? ? ? ? ? ? ? ? ? ? mVelocityTracker.clear(); ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? ……………………………… ? ? ? } break;
先判斷手指的移動距離,超過了移動的默認(rèn)距離,認(rèn)為是處于mIsBeingDragged狀態(tài),然后調(diào)用overScrollBy()函數(shù),這個方法是實現(xiàn)滾動的關(guān)鍵。并且該方法有個參數(shù)傳遞的是mOverscrollDistance,通過名字可以知道是超過滾動距離,猜測這個是預(yù)留的實現(xiàn)超過滾動邊界的變量。
進(jìn)入該方法看一下
protected boolean overScrollBy(int deltaX, int deltaY, ? ? ? ? ? ? int scrollX, int scrollY, ? ? ? ? ? ? int scrollRangeX, int scrollRangeY, ? ? ? ? ? ? int maxOverScrollX, int maxOverScrollY, ? ? ? ? ? ? boolean isTouchEvent) { ? ? ? ? final int overScrollMode = mOverScrollMode; ? ? ? ? final boolean canScrollHorizontal = ? ? ? ? ? ? ? ? computeHorizontalScrollRange() > computeHorizontalScrollExtent(); ? ? ? ? final boolean canScrollVertical = ? ? ? ? ? ? ? ? computeVerticalScrollRange() > computeVerticalScrollExtent(); ? ? ? ? final boolean overScrollHorizontal = overScrollMode == OVER_SCROLL_ALWAYS || ? ? ? ? ? ? ? ? (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal); ? ? ? ? final boolean overScrollVertical = overScrollMode == OVER_SCROLL_ALWAYS || ? ? ? ? ? ? ? ? (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical); ? ? ? ? int newScrollX = scrollX + deltaX; ? ? ? ? if (!overScrollHorizontal) { ? ? ? ? ? ? maxOverScrollX = 0; ? ? ? ? } ? ? ? ? int newScrollY = scrollY + deltaY; ? ? ? ? if (!overScrollVertical) { ? ? ? ? ? ? maxOverScrollY = 0; ? ? ? ? } ? ? ? ? // Clamp values if at the limits and record ? ? ? ? final int left = -maxOverScrollX; ? ? ? ? final int right = maxOverScrollX + scrollRangeX; ? ? ? ? final int top = -maxOverScrollY; ? ? ? ? final 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; ? ? ? ? } ? ? ? ? onOverScrolled(newScrollX, newScrollY, clampedX, clampedY); ? ? ? ? return clampedX || clampedY; ? ? }
ScrollView主要是豎直方向的滾動,主要看其Y軸方向的偏移??梢钥吹絥ewScrollY的范圍,top是-maxOverScrollY,bottom是maxOverScrollY + scrollRangeY,其中scrollRangeY是mScrollY的范圍值,maxOverScrollY是超過邊界的范圍值。如果newScrollY的值小于top或者大于bottom,會對該值進(jìn)行調(diào)整。
再進(jìn)入onOverScrolled()方法看看,
@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 = mScrollX; ? ? ? ? ? ? final int oldY = mScrollY; ? ? ? ? ? ? mScrollX = scrollX; ? ? ? ? ? ? mScrollY = scrollY; ? ? ? ? ? ? invalidateParentIfNeeded(); ? ? ? ? ? ? onScrollChanged(mScrollX, mScrollY, oldX, oldY); ? ? ? ? ? ? if (clampedY) { ? ? ? ? ? ? ? ? mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange()); ? ? ? ? ? ? } ? ? ? ? } else { ? ? ? ? ? ? super.scrollTo(scrollX, scrollY); ? ? ? ? } ? ? ? ? awakenScrollBars(); ? ? }
如果mScroller.isFinished()為false,說明正在滾動動畫中(包括fling和springBack)。如果沒有滾動動畫,則直接調(diào)用scrollTo到新的滑動到的mScrollY。再經(jīng)過繪制之后,就能看到界面滾動。
再回看overScrollBy()方法中,如果偏移距離到-maxOverScrollY與0之間,則是滑動超過上面邊界;如果偏移在scrollRangeY與maxOverScrollY + scrollRangeY之間,則是滑動超過下面邊界。
通過上面的分析可知,maxOverScrollY參數(shù)是預(yù)留的超過邊界的滑動距離,看一下傳遞過來的實參為成員變量mOverscrollDistance,改動一下該值應(yīng)該就可以實現(xiàn)超過邊界滑動了。但是發(fā)現(xiàn)成員變量為private,并且也沒提供修改的方法,所以改變該變量的值可以通過反射修改。
下面為修改
class OverScrollDisScrollView(cont: Context, attrs: AttributeSet?): ScrollView(cont, attrs) { ? ? val tag = "OverScrollDisScrollView" ? ? private val overScrollDistance = 500 ? ? constructor(cont: Context): this(cont, null) ? ? init { ? ? ? ? val sClass = ScrollView::class.java ? ? ? ? var field: Field? = null ? ? ? ? try { ? ? ? ? ? ? field = sClass.getDeclaredField("mOverscrollDistance") ? ? ? ? ? ? field.isAccessible = true ? ? ? ? ? ? field.set(this, overScrollDistance) ? ? ? ? } catch (e: NoSuchFieldException) { ? ? ? ? ? ? e.printStackTrace() ? ? ? ? } catch (e: IllegalAccessException) { ? ? ? ? ? ? e.printStackTrace() ? ? ? ? } ? ? ? ? overScrollMode = OVER_SCROLL_ALWAYS ? ? } }
這樣修改可以實現(xiàn)滑動超過邊界,不過有個問題,就是有時候松手了不能彈回,卡在超過邊界那了。需要看看手指抬起的代碼處理,經(jīng)過代碼調(diào)試發(fā)現(xiàn)問題出在手指抬起的下列代碼了
case MotionEvent.ACTION_UP: ? ? ? ? ? ? ? ? if (mIsBeingDragged) { ? ? ? ? ? ? ? ? ? ? final VelocityTracker velocityTracker = mVelocityTracker; ? ? ? ? ? ? ? ? ? ? velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); ? ? ? ? ? ? ? ? ? ? int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); ? ? ? ? ? ? ? ? ? ? if ((Math.abs(initialVelocity) > mMinimumVelocity)) {//手指抬起,有時不能彈回邊界 ? ? ? ? ? ? ? ? ? ? ? ? flingWithNestedDispatch(-initialVelocity); ? ? ? ? ? ? ? ? ? ? } else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, ? ? ? ? ? ? ? ? ? ? ? ? ? ? getScrollRange())) { ? ? ? ? ? ? ? ? ? ? ? ? postInvalidateOnAnimation(); ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? mActivePointerId = INVALID_POINTER; ? ? ? ? ? ? ? ? ? ? endDrag(); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? break;
假如現(xiàn)在手指向下滑動超過邊界的時候,計算出來的速度initialVelocity是正數(shù),取個負(fù)然后傳到方法flingWithNestedDispatch()函數(shù)
private void flingWithNestedDispatch(int velocityY) { ? ? ? ? final boolean canFling = (mScrollY > 0 || velocityY > 0) && ? ? ? ? ? ? ? ? (mScrollY < getScrollRange() || velocityY < 0); ? ? ? ? if (!dispatchNestedPreFling(0, velocityY)) { ? ? ? ? ? ? dispatchNestedFling(0, velocityY, canFling); ? ? ? ? ? ? if (canFling) { ? ? ? ? ? ? ? ? fling(velocityY); ? ? ? ? ? ? } ? ? ? ? } ? ? }
這個時候mScrollY小于0,velocityY小于0,所以canFling為false,導(dǎo)致后續(xù)的操作都不做了。這個時候,在界面上表現(xiàn)得就是卡在那里不動了。
超過邊界不彈回,這個問題怎么解決?經(jīng)過調(diào)試,找到以下方法,見代碼:
class OverScrollDisScrollView(cont: Context, attrs: AttributeSet?): ScrollView(cont, attrs) { ? ? val tag = "OverScrollDisScrollView" ? ? private val overScrollDistance = 500 ? ? constructor(cont: Context): this(cont, null) ? ? init { ? ? ? ? val sClass = ScrollView::class.java ? ? ? ? var field: Field? = null ? ? ? ? try { ? ? ? ? ? ? field = sClass.getDeclaredField("mOverscrollDistance") ? ? ? ? ? ? field.isAccessible = true ? ? ? ? ? ? field.set(this, overScrollDistance) ? ? ? ? } catch (e: NoSuchFieldException) { ? ? ? ? ? ? e.printStackTrace() ? ? ? ? } catch (e: IllegalAccessException) { ? ? ? ? ? ? e.printStackTrace() ? ? ? ? } ? ? ? ? overScrollMode = OVER_SCROLL_ALWAYS ? ? } // ? ?override fun onOverScrolled(scrollX: Int, scrollY: Int, clampedX: Boolean, clampedY: Boolean) { // ? ? ? ?super.onOverScrolled(scrollX, scrollY, clampedX, clampedY) // ? ?} ? ? override fun onTouchEvent(ev: MotionEvent?): Boolean { ? ? ? ? super.onTouchEvent(ev) ? ? ? ? if (ev != null) { ? ? ? ? ? ? when(ev.action) { ? ? ? ? ? ? ? ? MotionEvent.ACTION_UP -> { ? ? ? ? ? ? ? ? ? ? val yDown = getYDownScrollRange() ? ? ? ? ? ? ? ? ? ? //解決超過邊界松手不回彈得問題 ? ? ? ? ? ? ? ? ? ? if (mScrollY < 0) { ? ? ? ? ? ? ? ? ? ? ? ? scrollTo(0, 0) // ? ? ? ? ? ? ? ? ? ? ? ?onOverScrolled(0, 0, false, false) ? ? ? ? ? ? ? ? ? ? } else if (mScrollY > yDown) { ? ? ? ? ? ? ? ? ? ? ? ? scrollTo(0, yDown) // ? ? ? ? ? ? ? ? ? ? ? ?onOverScrolled(0, yDown, false, false) ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } ? ? ? ? return true ? ? } ? ? private fun getYDownScrollRange(): Int { ? ? ? ? var scrollRange = 0 ? ? ? ? if (childCount > 0) { ? ? ? ? ? ? val child = getChildAt(0) ? ? ? ? ? ? scrollRange = Math.max( ? ? ? ? ? ? ? ? 0, ? ? ? ? ? ? ? ? child.height - (height - mPaddingBottom - mPaddingTop) ? ? ? ? ? ? ) ? ? ? ? } ? ? ? ? return scrollRange ? ? } }
在onTouchEvent中最后,手指抬起的時候,加上一道判斷,如果這個時候是超過邊界的狀態(tài),彈回邊界。這樣基本上,可以解決問題。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android入門之實現(xiàn)自定義可復(fù)用的BaseAdapter
這篇文章主要為大家詳細(xì)介紹了Android如何構(gòu)建一個可復(fù)用的自定義BaseAdapter,文中的示例代碼講解詳細(xì),對我們學(xué)習(xí)Android有一定的幫助,需要的可以參考一下2022-11-11Android 實現(xiàn)帶角標(biāo)的ImageView(微博,QQ消息提示)
下面小編就為大家分享一篇Android 實現(xiàn)帶角標(biāo)的ImageView(微博,QQ消息提示),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-01-01android通過jxl讀excel存入sqlite3數(shù)據(jù)庫
本文主要介紹了android通過jxl去讀excel的內(nèi)容,然后存入sqlite3數(shù)據(jù)庫表,需要用到j(luò)xl的jar包和sqlite 的jar包,圖片是excel的數(shù)據(jù)格式,需要的朋友可以參考下2014-03-03Android 自定義View實現(xiàn)單擊和雙擊事件的方法
下面小編就為大家?guī)硪黄狝ndroid 自定義View實現(xiàn)單擊和雙擊事件的方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-09-09打飛機(jī)游戲終極BOSS Android實戰(zhàn)打飛機(jī)游戲完結(jié)篇
打飛機(jī)游戲終極BOSS,Android實戰(zhàn)打飛機(jī)游戲完結(jié)篇,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-07-07Android 應(yīng)用Crash 后自動重啟的方法小結(jié)
這篇文章主要介紹了Android 應(yīng)用Crash 后自動重啟的方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06Android Compose實現(xiàn)伸縮ToolBar的思路詳解
這篇文章主要介紹了Android Compose之伸縮ToolBar的實現(xiàn),本文給大家分享主要實現(xiàn)思路及實現(xiàn)過程,通過實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-10-10