網(wǎng)站首頁 編程語言 正文
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)超過滾動邊界的變量。
進入該方法看一下
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軸方向的偏移。可以看到newScrollY的范圍,top是-maxOverScrollY,bottom是maxOverScrollY + scrollRangeY,其中scrollRangeY是mScrollY的范圍值,maxOverScrollY是超過邊界的范圍值。如果newScrollY的值小于top或者大于bottom,會對該值進行調(diào)整。
再進入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),彈回邊界。這樣基本上,可以解決問題。
原文鏈接:https://blog.csdn.net/q1165328963/article/details/115344884
相關(guān)推薦
- 2024-03-14 AOP切面編程,以及自定義注解實現(xiàn)切面
- 2023-07-08 vscode上查看git的記錄,可以看到是誰多久前修改的代碼
- 2023-01-30 Android?Https證書過期的兩種解決方案_Android
- 2022-10-17 漫談C++哈夫曼樹的原理及實現(xiàn)_C 語言
- 2022-09-17 c語言實現(xiàn)從源文件從文本到可執(zhí)行文件經(jīng)歷的過程_C 語言
- 2022-03-07 C語言中的rand()和rand_r()詳解_C 語言
- 2022-06-13 C語言結(jié)構(gòu)體超詳細(xì)講解_C 語言
- 2022-06-30 Unity多屏幕設(shè)置的具體方案_C#教程
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支