Android?ScrollView實(shí)現(xiàn)滾動(dòng)超過邊界松手回彈
ScrollView滾動(dòng)超過邊界,松手回彈
Android原生的ScrollView滑動(dòng)到邊界之后,就不能再滑動(dòng)了,感覺很生硬。不及再多滑動(dòng)一段距離,松手后回彈這種效果順滑一些。
先查看下滾動(dòng)里面代碼的處理
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;先判斷手指的移動(dòng)距離,超過了移動(dòng)的默認(rèn)距離,認(rèn)為是處于mIsBeingDragged狀態(tài),然后調(diào)用overScrollBy()函數(shù),這個(gè)方法是實(shí)現(xiàn)滾動(dòng)的關(guān)鍵。并且該方法有個(gè)參數(shù)傳遞的是mOverscrollDistance,通過名字可以知道是超過滾動(dòng)距離,猜測(cè)這個(gè)是預(yù)留的實(shí)現(xiàn)超過滾動(dòng)邊界的變量。
進(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主要是豎直方向的滾動(dòng),主要看其Y軸方向的偏移??梢钥吹絥ewScrollY的范圍,top是-maxOverScrollY,bottom是maxOverScrollY + scrollRangeY,其中scrollRangeY是mScrollY的范圍值,maxOverScrollY是超過邊界的范圍值。如果newScrollY的值小于top或者大于bottom,會(huì)對(duì)該值進(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,說明正在滾動(dòng)動(dòng)畫中(包括fling和springBack)。如果沒有滾動(dòng)動(dòng)畫,則直接調(diào)用scrollTo到新的滑動(dòng)到的mScrollY。再經(jīng)過繪制之后,就能看到界面滾動(dòng)。
再回看overScrollBy()方法中,如果偏移距離到-maxOverScrollY與0之間,則是滑動(dòng)超過上面邊界;如果偏移在scrollRangeY與maxOverScrollY + scrollRangeY之間,則是滑動(dòng)超過下面邊界。
通過上面的分析可知,maxOverScrollY參數(shù)是預(yù)留的超過邊界的滑動(dòng)距離,看一下傳遞過來的實(shí)參為成員變量mOverscrollDistance,改動(dòng)一下該值應(yīng)該就可以實(shí)現(xiàn)超過邊界滑動(dòng)了。但是發(fā)現(xiàn)成員變量為private,并且也沒提供修改的方法,所以改變?cè)撟兞康闹悼梢酝ㄟ^反射修改。
下面為修改
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
? ? }
}這樣修改可以實(shí)現(xiàn)滑動(dòng)超過邊界,不過有個(gè)問題,就是有時(shí)候松手了不能彈回,卡在超過邊界那了。需要看看手指抬起的代碼處理,經(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)) {//手指抬起,有時(shí)不能彈回邊界
? ? ? ? ? ? ? ? ? ? ? ? flingWithNestedDispatch(-initialVelocity);
? ? ? ? ? ? ? ? ? ? } else if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0,
? ? ? ? ? ? ? ? ? ? ? ? ? ? getScrollRange())) {
? ? ? ? ? ? ? ? ? ? ? ? postInvalidateOnAnimation();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? mActivePointerId = INVALID_POINTER;
? ? ? ? ? ? ? ? ? ? endDrag();
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? break;假如現(xiàn)在手指向下滑動(dòng)超過邊界的時(shí)候,計(jì)算出來的速度initialVelocity是正數(shù),取個(gè)負(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);
? ? ? ? ? ? }
? ? ? ? }
? ? }這個(gè)時(shí)候mScrollY小于0,velocityY小于0,所以canFling為false,導(dǎo)致后續(xù)的操作都不做了。這個(gè)時(shí)候,在界面上表現(xiàn)得就是卡在那里不動(dòng)了。
超過邊界不彈回,這個(gè)問題怎么解決?經(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中最后,手指抬起的時(shí)候,加上一道判斷,如果這個(gè)時(shí)候是超過邊界的狀態(tài),彈回邊界。這樣基本上,可以解決問題。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Handler制作簡(jiǎn)單相冊(cè)查看器的實(shí)例代碼
下面小編就為大家分享一篇Handler制作簡(jiǎn)單相冊(cè)查看器的實(shí)例代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01
Android入門之實(shí)現(xiàn)自定義可復(fù)用的BaseAdapter
這篇文章主要為大家詳細(xì)介紹了Android如何構(gòu)建一個(gè)可復(fù)用的自定義BaseAdapter,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Android有一定的幫助,需要的可以參考一下2022-11-11
Android 實(shí)現(xiàn)帶角標(biāo)的ImageView(微博,QQ消息提示)
下面小編就為大家分享一篇Android 實(shí)現(xiàn)帶角標(biāo)的ImageView(微博,QQ消息提示),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01
android通過jxl讀excel存入sqlite3數(shù)據(jù)庫
本文主要介紹了android通過jxl去讀excel的內(nèi)容,然后存入sqlite3數(shù)據(jù)庫表,需要用到j(luò)xl的jar包和sqlite 的jar包,圖片是excel的數(shù)據(jù)格式,需要的朋友可以參考下2014-03-03
Android 自定義View實(shí)現(xiàn)單擊和雙擊事件的方法
下面小編就為大家?guī)硪黄狝ndroid 自定義View實(shí)現(xiàn)單擊和雙擊事件的方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-09-09
打飛機(jī)游戲終極BOSS Android實(shí)戰(zhàn)打飛機(jī)游戲完結(jié)篇
打飛機(jī)游戲終極BOSS,Android實(shí)戰(zhàn)打飛機(jī)游戲完結(jié)篇,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07
Android升級(jí)支持庫版本遇到的兩個(gè)問題詳解
安卓平臺(tái)其中一個(gè)很牛逼的地方在于它支持各種不同的設(shè)備。從你的平板電腦,到你的手機(jī),電視等,安卓無處不在。這篇文章主要給大家介紹了關(guān)于Android升級(jí)支持庫版本遇到的兩個(gè)問題,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面來一起看看吧。2017-10-10
Android 應(yīng)用Crash 后自動(dòng)重啟的方法小結(jié)
這篇文章主要介紹了Android 應(yīng)用Crash 后自動(dòng)重啟的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
Android Compose實(shí)現(xiàn)伸縮ToolBar的思路詳解
這篇文章主要介紹了Android Compose之伸縮ToolBar的實(shí)現(xiàn),本文給大家分享主要實(shí)現(xiàn)思路及實(shí)現(xiàn)過程,通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-10-10

