Android View視圖系統分析和Scroller和OverScroller分析
View 視圖分析
而ViewGroup也是對於View的一種實現。所以說全部的View元素在根本上都是一樣的。當然這並不等於說View == ViewGroup,就好比僅僅有ViewGroup才可以addView。
同理,這也就解釋了我們隊Canvas採取translate(平移)、clipRect(剪切)一系列變換的意義所在。那么Canvas沒有邊界。是不是就意味着我們的View資源一定都能如我們所願嗎。
Canvas是沒有邊界的,可是我們的Layout區域確實有邊界的。假設,我們希望看到我們的View子元素,那么他就必須在我們的視圖坐標(可視區域),可是在此處還要注意Layout區域並不一定都可見。

我相信在你第一眼看到這張圖片的時候你就能夠瞬間的對於Layout坐標(視圖區域)有了一個認識了。沒錯,你所示那些藍角紅線組成的長方形區域就是我們的布局區域,那么他們的框定是源自於哪里呢。
這里還記得我上面強調過的
LastCallMessageContainer.layout(left, top, left+ LastCallMessageContainer.getWidth(), top+ LastCallMessageContainer.getHeight());
這樣的方式來實現尾隨手指移動的View。可是這樣的方式好不好呢。答案非常明顯,並不好,有機會的話給打擊分享一下實現方式吧。
scrollTo()和scrollBy()分析
在View.java中提供了了例如以下兩個變量以及對應的屬性方法去讀取滾動值 ,例如以下: View.java類中
- /**
- * The offset, in pixels, by which the content of this view is scrolled
- * horizontally.
- * {@hide}
- */
- protected int mScrollX; //該視圖內容相當於視圖起始坐標Layout的偏移量 , X軸 方向
- /**
- * The offset, in pixels, by which the content of this view is scrolled
- * vertically.
- * {@hide}
- */
- protected int mScrollY; //該視圖內容相當於視圖起始坐標Layout的偏移量 , Y軸方向
- /**
- * Return the scrolled left position of this view. This is the left edge of
- * the displayed part of your view. You do not need to draw any pixels
- * farther left, since those are outside of the frame of your view on
- * screen.
- *
- * @return The left edge of the displayed part of your view, in pixels.
- */
- public final int getScrollX() {
- return mScrollX;
- }
- /**
- * Return the scrolled top position of this view. This is the top edge of
- * the displayed part of your view. You do not need to draw any pixels above
- * it, since those are outside of the frame of your view on screen.
- *
- * @return The top edge of the displayed part of your view, in pixels.
- */
- public final int getScrollY() {
- return mScrollY;
- }

public void scrollTo(int x, int y)
說明:在當前視圖內容偏移至(x , y)坐標處,即位於Layout區域(x , y)坐標處。
方法原型為: View.java類中
- /**
- * 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;
- //回調onScrollChanged方法
- onScrollChanged(mScrollX, mScrollY, oldX, oldY);
- if (!awakenScrollBars()) {
- invalidate(); //一般都引起重繪
- }
- }
- }
public void scrollBy(int x, int y)
說明:在當前視圖內容在Layout布局中繼續偏移(x , y)個單位。
方法原型為: View.java類中
- /**
- * 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
- */
- // 看出原因了吧 。
。
mScrollX 與 mScrollY 代表我們當前偏移的位置 , 在當前位置繼續偏移(x ,y)個單位
- public void scrollBy(int x, int y) {
- scrollTo(mScrollX + x, mScrollY + y);
- }
大家自己想一想。
Scroller 和OverScroller分析
我們知道想把一個View的內容偏移至指定坐標(x,y)處,利用scrollTo()方法直接調用就OK了。但我們不能忽視的是,該方法本身來的的副作用:很迅速的將View/ViewGroup偏移至目標點,而沒有對這個偏移過程有不論什么控制。對用戶而言可能是不太友好的。於是,基於這樣的偏移控制,Scroller類被設計出來了,該類的主要作用是為偏移過程制定一定的控制流程(后面我們會知道的很多其它),從而使偏移更流暢,更完美。
可能上面說的比較懸乎,道理也沒有講透。以下我就依據特定情景幫助大家分析下:
情景: 從上海怎樣到武漢?
普通的人可能會想,so easy : 飛機、輪船、11路公交車...
文藝的人可能會想, 小 case : 時空忍術(火影的招數)、翻個筋斗(孫大聖的招數)...
無論怎么樣,我們想出來的套路可能有兩種:
1、有個時間控制過程才干抵達(緩慢的前進) ----- 相應於Scroller的作用
如果做火車,這個過程可能包含: 火車速率,花費周期等。
2、瞬間移動(超神太快了。都眩暈了,用戶體驗不太好) ------ 相應於scrollTo()的作用
我們看看View里面的computeScroll()做了些什么
/** * Called by a parent to request that a child update its values for mScrollX * and mScrollY if necessary. This will typically be done if the child is * animating a scroll using a {@link android.widget.Scroller Scroller} * object. */ public void computeScroll() { }
空的。意思非常明顯。自己加入。
computeScroll()方法介紹
為了易於控制滑屏控制,Android框架提供了 computeScroll()方法去控制這個流程。在繪制View時。會在draw()過程調用該方法。因此。 再配合使用Scroller實例。我們就能夠獲得當前應該的偏移坐標,手動使View/ViewGroup偏移至該處。
computeScroll()方法原型例如以下,該方法位於ViewGroup.java類中
- /**
- * Called by a parent to request that a child update its values for mScrollX
- * and mScrollY if necessary. This will typically be done if the child is
- * animating a scroll using a {@link android.widget.Scroller Scroller}
- * object.
- */由父視圖調用用來請求子視圖依據偏移值 mScrollX,mScrollY又一次繪制
- public void computeScroll() { //空方法 。自己定義ViewGroup必須實現方法體
- }
為了實現偏移控制,一般自己定義View/ViewGroup都須要重載該方法 。
其調用過程位於View繪制流程draw()過程中,例如以下:
- @Override
- protected void dispatchDraw(Canvas canvas){
- ...
- for (int i = 0; i < count; i++) {
- final View child = children[getChildDrawingOrder(count, i)];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
- more |= drawChild(canvas, child, drawingTime);
- }
- }
- }
- protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
- ...
- child.computeScroll();
- ...
- }
其實講到這里,有的同學可能比較迷惑,OverScroller和Scroller有什么差別呢?其實,這兩個類都屬於Scrollers,Scroller出現的比較早,在API1就有了。OverScroller是在API9才加入上的,出現的比較晚,所以功能比較完好,Over的意思就是超出。即OverScroller提供了對超出滑動邊界的情況的處理,這兩個類80%的API是一致的。OverScroller比Scroller加入了一下幾個方法
☞ isOverScrolled()
☞ springBack(int startX, int startY, int minX, int maxX, int minY, int maxY)
☞ fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY)
☞ notifyHorizontalEdgeReached(int startX, int finalX, int overX)
☞ notifyVerticalEdgeReached(int startY, int finalY, int overY)
從名字也能看出來。都是對Over功能的支持。其它的API都一樣。所以介紹通用API的時候。並不區分OverScroller和Scroller。
以下簡介一下經常使用的API。
☞ startScroll(int startX, int startY, int dx, int dy)
☞ startScroll(int startX, int startY, int dx, int dy,int duration)
核心用來開啟滾動的方法:
參數
startX 滾動起始值
startY 滾動起始值
dx 水平方向滑動的距離,正值會使滾動向左滾動
dy 垂直方向滑動的距離。正值會使滾動向上滾動
duration 滾動持續時間,以毫秒計。缺省值250ms作為持續時間。
☞ computeScrollOffset() 這個函數非常核心,表面上來看他僅僅是用來推斷我們的滾動是否結束
if (mScroller.computeScrollOffset())看起來似乎沒有什么價值,起始不然
//依據當前已經消逝的時間計算當前的坐標點,保存在mCurrX和mCurrY值中 public boolean computeScrollOffset() { if (mFinished) { //已經完畢了本次動畫控制,直接返回為false return false; } ....... mScrollerX.updateScroll(q); mScrollerY.updateScroll(q); ....... }
這就是為什么我們稱Scroller為滾動的助手類,原來如此,我們在startScroll中設好初始值 、滾動距離、滾動時間。那么
computeScrollOffset會幫我們計算出在特定的時間內應該滾動在什么地方,到時候我們僅僅要通過getCurrX()和getCurrY()得到就能夠了
☞ getCurrX() 這個就是獲取當前滑動的坐標值,由於Scrollers僅僅是一個輔助計算類,所以假設我們想獲取滑動時的時時坐標,就能夠通過這種方法獲得。然后在computeScroll()里面調用
☞ getFinalX() 這個是用來獲取終於滑動停止時的坐標
☞ isFinished() 用來推斷當前滾動是否結束
那么 。到了這里我們就一目了然。Scroller的基本用法不外乎:
mScroller.startScroll(<span style="color: rgb(51, 51, 51); font-family: 宋體;font-size:18px; line-height: 30px; text-indent: 28px;">startX ,<span style="color: rgb(51, 51, 51); font-family: 宋體;font-size:18px; line-height: 30px; text-indent: 28px;">startY,dx,dy,duration</span></span>);
@Override public void computeScroll() { // 先推斷mScroller滾動是否完畢 if (mScroller.computeScrollOffset()) { // 這里調用View的scrollTo()完畢實際的滾動 scrollTo( mScroller.getCurrX(), mScroller .getCurrY()); // 必須調用該方法,否則不一定能看到滾動效果 invalidate(); } super.computeScroll(); }
那么到了這里,基本上我們對於Scroller的基本使用應該就不是問題了。
今天就到這里吧。下次再寫個高於高級使用續篇吧。