想象一下你拿着放大鏡貼很近的看一副巨大的清明上河圖,
那放大鏡裏可以看到的內容是很有限的,
而隨着放大鏡的上下左右移動,就可以看到不同的內容了
android中手機屏幕就相當於這個放大鏡, 而看到的內容是畫在一個無限大的畫布上~
畫的內容有限, 而手機屏幕可以看到的東西更有限~ 但是背景畫布是無限的
如果把放大鏡的移動比作scroll操作,那麼可以理解,這個scroll的距離是無限制的~
只不過scroll到有圖的地方纔能看到內容
參考ScrollView理解, 當child內容過長時,有一部分內容是"看不到"的,相當於"在屏幕之外",
而隨着我們的拖動滾動,則慢慢看到剩下的內容,相當於我們拿着放大鏡向下移動~
而代碼中的這個scroll方法系統提供了兩個:
scrollTo和scrollBy
源碼如下
/**
* Set the scrolled position of your view. This
will cause a call to
* {@link
#onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x
the x position to scroll to
* @param y
the y position to scroll to
*/
public void scrollTo (int x, int y)
{
if (mScrollX !=
x || mScrollY !=
y) {
int oldX
= mScrollX;
int oldY
= mScrollY;
mScrollX =
x;
mScrollY =
y;
invalidateParentCaches();
onScrollChanged( mScrollX, mScrollY,
oldX, oldY);
if (!awakenScrollBars())
{
postInvalidateOnAnimation();
}
}
}
/**
* Move the scrolled position of your view. This
will cause a call to
* {@link
#onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x
the amount of pixels to scroll by horizontally
* @param y
the amount of pixels to scroll by vertically
*/
public void scrollBy( int x, int y)
{
scrollTo (mScrollX +
x, mScrollY +
y);
}
mScrollX 表示離視圖起始位置的x水平方向的偏移量
mScrollY表示離視圖起始位置的y垂直方向的偏移量
可以通過getScrollX() 和getScrollY()方法分別獲得
兩個方法的區別就是to參數是絕對值,by是相對於當前滾動到的位置的增量值
比如:
mScrollX=100, mScrollY=100
scrollTo(20, 20) -> mScrollX=20, mScrollY=20;
scrollBy(20, 20) -> mScrollX=120,mScrollY=120;
注意:
這裏mScrollX和mScrollY的值是偏移量,是相對於視圖起始位置的偏移量~
所以任何view,無論佈局是怎麼樣的,只要是剛初始化未經過scroll的,偏移量都是0~
即mScrollX/Y是相對於自己初始位置的偏移量,而不是相對於其容器的位置座標
-----------------------------------------------------------------------------
下面是就ScrollView的源碼拆開了的分析,並加入了一些補充擴展,
主要內容包括
1.最基本的隨着touch滾動的效果
2.fling效果,即滑動後擡起手後繼續關心滾動的效果
3.over
scroll效果,即拖動超出邊界的處理
上述123系統都有提供相關實現方法,但是ScrollView默認只有1,2的實現效果,
over
scroll需要我們自行進行一定處理後纔可以看到~
下面就ScrollView的源碼進行分析,且提供三個自定義ScrollView(難度依次遞進)實現上面的三種效果,已打包成demo
後面源碼分析時,系統是亂七八糟直接寫一起時,分析的被比較細也比較亂,
demo中三個自定義ScrollView相當於按照難度梯度抽取出來的,
即view2是在view1基礎上修改添加功能的,view3是在view2基礎上修改添加功能的
可以從demo下手幫助理解其中原理
-----------------------------------------------------------------------------
scroll相當於一個拖動,我們可以用scrollTo/By控制其滾動到某個位置,
那一般ScrollView控件這種都是隨着我們的手勢生效的,內部原理是如何的呢~
下面來研究下系統ScrollView控件源碼裏面的具體實現~
系統考慮的東西比較多,研究起來較爲複雜,所以先就核心部分拆開一點點研究~
手的拖動肯定是跟touch即觸摸事件掛鉤了~直接定位到ScrollView中的該方法
(onTouchEvent幹什麼用的就不掃盲了)
首先是ACTION_DOWN
case MotionEvent.ACTION_DOWN:
{
mIsBeingDragged = getChildCount() != 0;
if (!mIsBeingDragged)
{
return false;
}
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if (!mScroller.isFinished())
{
mScroller.abortAnimation();
if (mFlingStrictSpan != null)
{
mFlingStrictSpan.finish();
mFlingStrictSpan = null;
}
}
// Remember where the motion event started
mLastMotionY = ev.getY();
mActivePointerId = ev.getPointerId(0);
break;
}
有三段代碼,第二三段帶註釋,下面是介紹:
1.ScrollView沒有child則不做處理,這個不解釋,都沒child滾個蛋啊
如果有child則設置標誌位mIsBeingDragged即"開始拖動"(看英文就可以理解了)
2.看註釋理解~如果還在滑動用戶觸碰了屏幕,則立刻停止滑動
mScroller是一個OverScroller對象,是處理滾動的,
類介紹裏提到這個功能和Scroller類差不多,大部分情況下可以替代之,
區別可以簡單的理解爲OverScroller允許超出邊界,後面會介紹Scroller類~
至於mFlingStrictSpan無視之
3.看註釋理解~記住點擊的位置
scrollerView,處理垂直滾動,這裏就只記錄Y座標了
mActivePointerId是用來處理多點觸控時的穩定性的,這裏先記住作用就行了
-----------------------------------------------------------------------------
然後是ACTION_MOVE,這個是重點,隨着move我們希望控件也能隨着我們的手的拖動滾動到所需位置
case MotionEvent.ACTION_MOVE:
if (mIsBeingDragged
) {
//
Scroll to follow the motion event
final int activePointerIndex
= ev.findPointerIndex(mActivePointerId);
final float y
= ev.getY(activePointerIndex);
final int deltaY
= ( int) ( mLastMotionY - y);
mLastMotionY = y;
final int oldX
= mScrollX;
final int oldY
= mScrollY;
final int range
= getScrollRange();
final int overscrollMode
= getOverScrollMode();
final boolean canOverscroll
= overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS
&& range > 0);
if (overScrollBy(0,
deltaY, 0, mScrollY,
0, range, 0, mOverscrollDistance, true))
{
//
Break our velocity if we hit a scroll barrier.
mVelocityTracker.clear();
}
onScrollChanged( mScrollX, mScrollY , oldX,
oldY);
if (canOverscroll)
{
final int pulledToY
= oldY + deltaY;
if (pulledToY
< 0) {
mEdgeGlowTop.onPull((float)
deltaY / getHeight());
if (!
mEdgeGlowBottom.isFinished()) {
mEdgeGlowBottom.onRelease();
}
} else if (pulledToY
> range) {
mEdgeGlowBottom.onPull((float)
deltaY / getHeight());
if (!
mEdgeGlowTop.isFinished()) {
mEdgeGlowTop.onRelease();
}
}
if (
mEdgeGlowTop != null
&& (! mEdgeGlowTop.isFinished()
|| !mEdgeGlowBottom.isFinished())) {
invalidate();
}
}
}
break;
系統註釋還是很良心的,一般以功能點爲單位註釋,這裏還是根據註釋去看
分兩段:隨着觸摸事件的滾動,還有頂部底部邊緣陰影的處理
陰影處理的先忽視
這裏的觸摸點獲取不是直接event.getY,而是通過下面兩句代碼獲取的
final int activePointerIndex
= ev.findPointerIndex( mActivePointerId);
final float y
= ev.getY(activePointerIndex);
上面已經介紹過了mActivityPointerId的保證多點觸碰穩定性的作用,
包括onTouchEvent裏面的ACTION_POINTER_DOWN/UP也是爲了處理多點情況的,
爲了不發散太多就不細介紹了(其實我也不是研究太透徹)
知道這樣處理能防止多點觸控的干擾,可以穩定獲取到我們需要的觸摸的y座標就行了
根據現在的觸摸座標y和上次位置的y座標mLastMotionY算出差值,即這次移動的距離deltaY
最後以獲取到的這些數據進行滾動操作~
ScrollView中在這裏使用的是overScrollBy方法,該方法是其父類view的方法,定位過去看下
其實如果要簡單處理的話直接掉scrollTo方法就可以了~參見demo中MyScrollView1
如果覺得ScrollView裏邏輯無法理解,那就可以先把上面demo的view1研究懂以後再繼續下文
注意,scroll由於是沒有限制的,即可以滾動到任何位置,顯然不符合我們的需要,
所以我們要限制滾動範圍,只在有內容的繪製部分滾動
由於ScrollView只是縱向Y軸上滾動,所以只限定y上滾動範圍即可,
如下圖示,紅框是scrollview,藍框是child,滾動範圍應該是箭頭所示部分~
即0
到 child.height-scrollview,height
而demo裏view1中也添加了這麼一段(demo是橫向滾動)
// Clamp values if at the limits and record
final int left
= 0;
final int right
= getScrollRangeX();
// 防止滾動超出邊界
if(scrollX
> right) {
scrollX = right;
} else if(scrollX
< left) {
scrollX = left;
}
-----------------------------------------------------------------------------
因爲scrollView考慮的比較多,所以處理麻煩點,按照源碼追蹤到view中的overScrollerBy方法
/**
* Scroll the view with standard behavior for scrolling beyond the normal
* content boundaries. Views that call this method should override
* {@link #onOverScrolled(int, int, boolean, boolean)} to respond to the
* results of an over -scroll operation.
*
* Views can use this method to handle any touch or fling -based scrolling.
*
* @param deltaX Change in X in pixels
* @param deltaY Change in Y in pixels
* @param scrollX Current X scroll value in pixels before applying deltaX
* @param scrollY Current Y scroll value in pixels before applying deltaY
* @param scrollRangeX Maximum content scroll range along the X axis
* @param scrollRangeY Maximum content scroll range along the Y axis
* @param maxOverScrollX Number of pixels to overscroll by in either direction
* along the X axis.
* @param maxOverScrollY Number of pixels to overscroll by in either direction
* along the Y axis.
* @param isTouchEvent true if this scroll operation is the result of a touch event.
* @return true if scrolling was clamped to an over -scroll
boundary along either
* axis, false otherwise.
*/
@SuppressWarnings({"UnusedParameters"})
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;
}
方法的作用:
總結起來就是計算over
scroll時scrollX/Y的值~ 並將其記錄在onOverScrolled方法裏
參數意義:
前8個分兩組,1357是針對x軸的,2468則是y軸的,這裏scrollView只縱向滾動,所以只處理y軸
比較難理解的是後倆參數
1.scrollRange
是某方向上滾動的範圍,可以參考上面的圖片,只不過要把padding部分考慮進去
下面是系統獲取範圍的方法
private int getScrollRange ()
{
int scrollRange
= 0;
if (getChildCount()
> 0) {
View child = getChildAt(0);
scrollRange = Math. max(0,
child.getHeight() - (getHeight()
- mPaddingBottom - mPaddingTop));
}
return scrollRange;
}
不難理解,最大距離的情況就是childview移動到了最頂部,然後滑動到最底部,上面這個方法算的就是這個最大距離
2.maxOverScroll
爲越界滾動最大距離,即在之前範圍的基礎上再加上這個越界最大距離~
ScrollView在這裏設置的是系統默認值0
比如以前縱向的滾動範圍是0~300,那如果這個值設爲50,則最終over scroll的範圍就是-50~350,方法內算法如下
//
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;
top=-maxOverScrollY ~ bottom=maxOverScrollY+scrollRangY
帶入我們假設的值,那就是0~300, 注意,這個300是Y座標300~
這裏假設我們的maxOverScrollY不是系統默認的Y而是
50,
那雖然滾動範圍不變還是500-200=300~
但是實際上可以滾動的範圍是大於300的~
效果類似於ios那種,listview到達頂部以後繼續拖還可以移動~
也可以腦補下拉刷新listview的效果
在拖到頂部以後,還可以overScroll繼續拖動,
而繼續拖動的最大距離就是maxOverScrollY,如圖左邊小箭頭的長度
底部同理
那整個邊界範圍就變了~
從原來的y
= 0 ~ 300 變成了
-maxOverScrollY
~ scrollRangeY + maxOverScrollY
看圖很好理解,就變成了
y
= -50 ~ 350
橫向同理
下面代碼就是判斷了
如果現在滾動的新座標超過了over的極限值,則將極限值賦值給新座標~
簡單而言就是滑動到極限越界距離以後就卡住他,讓他"劃不動",
這裏英文clamp爲"鉗住"的意思,英語好的可以幫助快速理解~
x/y方向達到極限距離的同時會分別記錄下一個標誌符,最後|作爲返回值,
即任何一個方向上有劃不動的情況時則返回true,否則false
最後通過onOverScrolled暴露給子類,這裏view裏面的onOverScrolled方法是empty不做任何處理的~裏面註釋也是賣萌,"我是故意的~"
上面一大串overScrollBy方法源碼分析這裏總結一下
該方法就相當於在scrollTo/By的基礎上添加了對overScroll情況的處理,
但父類view中只處理數據,沒有實際的scroll操作,父類view處理完數據後將其記錄在onOverScrolled方法中,
子類繼承onOverScrolled方法再根據得到的數據scrollTo/By處理即可~
舉個簡單的例子幫助理解,比如這個view相當於一個大加工廠,
那overScrollBy方法相當於一個加工車間,比如是做串串的(竹籤上有海帶素雞一類的那種)-
-
把原材料加工好成串串後,直接就丟到一個儲藏的倉庫裏~不做任何其他操作
而繼承view的子類ScrollView就相當於來拿貨的銷售商,來了以後不管生產過程,直接去這個儲藏的倉庫裏,
把貨拿出來然後該賣的賣,該自己吃的自己吃,該二次加工的二次加工~進行具體的操作~
這個倉庫就可以理解爲onOverScrolled方法~
可以理解爲一個監聽,比如scrollview提供一個onScroll監聽,父類只用該方法記錄數據,
而子類複寫之就可以獲取到所需數據,如當前滾動到位置,根據需要處理了
回到源碼ScrollView的onOverScrolled方法
其實簡單處理的話直接在onOverScrolled裏面scrollTo就可以了,但是系統考慮到scrollbar,animating
scroll等情況所以處理的比較複雜
如果下面這段代碼無法理解,可以先跳過本段,直接到ACTION_UP部分,
可以在看完後面OverScroll實現(demo中view3)介紹,研究懂後再回頭看這段代碼
@Override
protected void onOverScrolled( int scrollX, int scrollY,
boolean clampedX, boolean clampedY)
{
//
Treat animating scrolls differently; see #computeScroll() for why.
if (!mScroller.isFinished())
{
mScrollX = scrollX;
mScrollY = scrollY;
invalidateParentIfNeeded();
if ( clampedY)
{
mScroller.springBack(mScrollX,
mScrollY, 0, 0, 0, getScrollRange());
}
} else {
super.scrollTo(scrollX,
scrollY);
}
awakenScrollBars();
}
這裏是if分成兩部分的,註釋說明爲,對於還在進行中的滾動要區別處理,
區分處理的原因可以參考computeScroll()方法
@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 oldX = mScrollX;
int oldY = mScrollY;
int x = mScroller.getCurrX();
int y = mScroller.getCurrY();
if (oldX != x || oldY != y) {
final int range
= getScrollRange();
final int overscrollMode
= getOverScrollMode();
final boolean canOverscroll
= overscrollMode == OVER_SCROLL_ALWAYS ||
(overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
range > 0);
overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, range,
0, mOverflingDistance, false);
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (canOverscroll) {
if (y < 0 && oldY >=
0) {
mEdgeGlowTop.onAbsorb(( int) mScroller.getCurrVelocity());
} else if (y
> range && oldY <= range) {
mEdgeGlowBottom.onAbsorb(( int) mScroller.getCurrVelocity());
}
}
}
awakenScrollBars();
// Keep on drawing until the animation has finished.
postInvalidate();
} else {
if (mFlingStrictSpan != null)
{
mFlingStrictSpan.finish();
mFlingStrictSpan = null;
}
}
}
註釋翻譯下
這個方法會在viewgroup draw繪製的時候調用,
我們不想在這個時候再次顯示scrollbar滾動條,這個scrollTo方法會處理,
所以我們在這裏複製了scrollTo方法的大部分內容
下面代碼和onTouch裏的MOVE中代碼一樣(上文有引用過這部分代碼)
回到onOverScrolled方法中,結合看意思就是
如果mScroller.isFinished滾動動畫已經結束了,那正常scrollTo方法滾動
如果未結束,那調用一個不會顯示scrollBar滾動條的scrollTo方法
(上面註釋說過,處理原因就是爲了方法滾動條再次顯示,
且代碼大部分複製scrollTo,即相當於一個不顯示滾動條的scrollTo方法)
且如果未滾動完成,還要加個判斷, 如果是滾動到不能再滾動了,即clampedY=true
則進行回彈操作mScroller.springBack
再次引用上面車間加工串串的例子加深理解
demo中自己處理就相當於,賣串串的自己加工串串(計算數據), 直接賣(scrollTo)~
系統的ScrollView呢,就是把串串交給加工車間view精加工一下(overScrollBy),
最後從onOverScrolled倉庫中取加工好的串串再去賣(scrollTo)
-----------------------------------------------------------------------------
回到onTouch,之後是ACTION_UP操作
按照平常滾屏的習慣,UP擡起時,一般還會有一個慣性繼續滾動一段距離~
(這裏我們把滾動叫做scroll,這個有慣性的甩~拋~的動作叫做fling~)
首先用VelocityTracket獲取y向的速度,根據速度去處理fling
(按照通常思維,速度越快,甩的越遠~)
case MotionEvent. ACTION_UP:
if ( mIsBeingDragged)
{
final VelocityTracker
velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity =
( int) velocityTracker.getYVelocity(mActivePointerId );
if (getChildCount() >
0) {
if ((Math. abs(initialVelocity)
> mMinimumVelocity)) {
fling(-initialVelocity);
} else {
if ( mScroller.springBack(mScrollX,
mScrollY, 0, 0, 0,
getScrollRange())) {
invalidate();
}
}
}
mActivePointerId = INVALID_POINTER;
endDrag();
}
break;
這裏有判斷,當速度超過一個最小閥值的時候,就視爲一個fling~則調用fling()方法
否則視爲普通的scroll,那判斷這個時候是否需要回彈操作,有的話invalidate刷新頁面
下面是核心方法~甩~
/**
* Fling the scroll view
*
* @param velocityY
The initial velocity in the Y direction. Positive
* numbers mean that the finger/cursor is moving down the screen,
* which means we want to scroll towards the top.
*/
public void fling(int velocityY)
{
if (getChildCount() > 0) {
int height = getHeight() - mPaddingBottom
- mPaddingTop;
int bottom = getChildAt(0).getHeight();
mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0,
Math. max(0, bottom - height), 0, height/2);
final boolean movingDown
= velocityY > 0;
if (mFlingStrictSpan == null)
{
mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling" );
}
invalidate();
}
}
諸如這類"高級"的滾動,都使用了Scroller類處理~詳見
fling裏面實際上調用的是mScroller的fling方法,我們定位到OverScroller類該方法
內部原理太多,這裏只解釋方法的作用和參數的意義了
方法作用:根據一個fling手勢開始fling行爲~移動的距離取決於fling的初始速度~
參數傳進來的值爲
mScrollX, mScrollY, 0, velocityY, 0, 0, 0, Math. max(0, bottom - height), 0, height/2
一共10個參數分兩組,奇數爲x軸處理,偶爲y
2 4 6 8 10五個參數分別介紹(x向的同理)
startY 開始甩的y座標 - 值爲當前滾動位置mScrollY
velocityY y軸上的速度 - 值爲velocityTracker計算出來的速度值
minY y軸上可以到的最小座標 - 值爲0
maxY y抽上可以到的最大座標 - 值爲child的高度減去viewgroup除padding以外的高度~
(這個值和之前計算的scrollRangeY一樣,只不過那個是長度,這個是座標~
想理解可以參考之前的圖片)
overY 越界滑動的範圍 - 值爲viewgroup除padding高度以外的一半
fling裏面的具體速度距離的算法...略過~
插一句
如果是沒有over的scroll或者fling,那直接用Scroller類就可以了,系統考慮的比較全,
所以ScrollView裏面用的是OverScroller類~
同樣的fling方法Scroller就沒有最後兩個和over相關的參數
下面是一個不考慮over情況用Scroller實現fling效果的demo
-----------------------------------------------------------------------------
回到over scroll的處理
上面提到,系統ScrollView源碼中是有處理over scroll的,只不過ScrollView中的對應參數overDistance爲0,造成了ScrollView沒有over
scroll的效果
這裏我們可以自己嘗試實現下~
其實非常簡單,demo12中ouTouch的MOVE裏面的scrollTo方法改成和ScrollView一樣的,
即利用父類view的overScrollBy方法轉一圈~
只不過調用overScrollBy方法時,記得傳入的maxOverScrollX/Y不能是0,不然就沒意義了
這樣拖動的時候就可以over scroll了~
既然有over scroll那我們肯定需要回彈效果,即鬆手後自動滾動回來~
前面說過這種高級的要用Scroller類,而牽涉到over的,自然就用到OverScroller類了~
fling方法和Scroller一樣,多一個overX方法,即fling時over scroll的最大距離,
demo裏我設定成了maxOverScrollX的一半,可以自行調整
fling的回彈是自動的,但我們fling行爲是有個最小速度判斷的,因而在UP時還要加個,
沒有fling時,如果開始了一個回彈效果,則刷新視圖~
以下是over scroll的優化效果
over scroll從體驗上來說,我們希望是有一個阻塞的效果的,
即如果普通狀態下,手指移動100距離,那view也滾動100距離,
但是over scroll時,手指移動100距離,view滾動距離應該按比例降低~
這樣一種效果才更加"真實"
此外demo中還在onOverScrolled回調用添加了一個拉斷效果,
即當拖動到over scroll的極限距離時,雖然沒有UP但是強制進行回彈操作,
相當於模擬了一個"拉斷"的效果
-----------------------------------------------------------------------------
缺陷, 沒有考慮measure,所以MyScrollView中子類size有點問題,demo中暫時寫死了child寬度,
measure的原理介紹會在後面有時間整理出~希望大家持續關注