Android學習Scroller(五)——詳解Scroller調用過程以及View的重繪

來源:互聯網
上載者:User

Android學習Scroller(五)——詳解Scroller調用過程以及View的重繪
MainActivity如下:

package cc.ww;import android.os.Bundle;import android.widget.ImageView;import android.widget.ImageView.ScaleType;import android.widget.RelativeLayout;import android.widget.RelativeLayout.LayoutParams;import android.app.Activity;import android.content.Context;public class MainActivity extends Activity {private Context mContext;private int [] imagesArray;    private ScrollLauncherViewGroup mScrollLauncherViewGroup;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);init();}private void init(){mContext=this;imagesArray=new int []{R.drawable.a,R.drawable.b,R.drawable.c,R.drawable.d};mScrollLauncherViewGroup=new ScrollLauncherViewGroup(mContext);ImageView imageView=null;RelativeLayout.LayoutParams layoutParams=null;for (int i = 0; i < imagesArray.length; i++) {imageView=new ImageView(mContext);imageView.setScaleType(ScaleType.FIT_XY);imageView.setImageResource(imagesArray[i]);layoutParams=new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);imageView.setLayoutParams(layoutParams);mScrollLauncherViewGroup.addView(imageView);}setContentView(mScrollLauncherViewGroup);}}

ScrollLauncherViewGroup如下:
package cc.ww;import android.content.Context;import android.graphics.Canvas;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.widget.Scroller;import android.widget.Toast;/** * Scroller原理: * 為了讓View或者ViewGroup的內容發生移動,我們常用scrollTo()和scrollBy()方法. * 但這兩個方法執行的速度都很快,瞬間完成了移動感覺比較生硬. * 為了使View或者ViewGroup的內容發生移動時比較平滑或者有其他的移動漸層效果 * 可採用Scroller來實現. * 在具體實現時,我們繼承並重寫View或者ViewGroup時可產生一個Scroller由它來具體 * 掌控移動過程和結合插值器Interpolator調用scrollTo()和scrollBy()方法. *  *  * Scroller的兩個主要構造方法: * 1 public Scroller(Context context) {} * 2 public Scroller(Context context, Interpolator interpolator){} * 採用第一個構造方法時,在移動中會採用一個預設的插值器Interpolator * 也可採用第二個構造方法,為移動過程指定一個插值器Interpolator *  *  * Scroller的調用過程以及View的重繪: * 1 調用public void startScroll(int startX, int startY, int dx, int dy) *   該方法為scroll做一些準備工作. *   比如設定了移動的起始座標,滑動的距離和方向以及期間等. *   該方法並不是真正的滑動scroll的開始,感覺叫prepareScroll()更貼切些. *    * 2 調用invalidate()或者postInvalidate()使View(ViewGroup)樹重繪 *   重繪會調用View的draw()方法 *   draw()一共有六步:  *   Draw traversal performs several drawing steps which must be executed    *   in the appropriate order:    *   1. Draw the background    *   2. If necessary, save the canvas' layers to prepare for fading    *   3. Draw view's content    *   4. Draw children    *   5. If necessary, draw the fading edges and restore layers    *   6. Draw decorations (scrollbars for instance) *   其中最重要的是第三步和第四步 *   第三步會去調用onDraw()繪製內容 *   第四步會去調用dispatchDraw()繪製子View *   重繪分兩種情況: *   2.1 ViewGroup的重繪 *       在完成第三步onDraw()以後,進入第四步ViewGroup重寫了 *       父類View的dispatchDraw()繪製子View,於是這樣繼續調用: *       dispatchDraw()-->drawChild()-->child.computeScroll(); *   2.2 View的重繪 *       我們注意到在2提到的"調用invalidate()".那麼對於View它又是怎麼 *       調用到了computeScroll()呢?View沒有子View的.所以在View的源碼裡可以 *       看到dispatchDraw()是一個空方法.所以它的調用路徑和ViewGroup是不一樣的. *       在此不禁要問:如果一個ButtonSubClass extends Button 當mButtonSubClass *       執行mButtonSubClass.scrollTo()方法時怎麼觸發了ButtonSubClass類中重寫 *       的computeScroll()方法??? *       在這裡我也比較疑惑,只有藉助網上的資料和源碼去從invalidate()看起. *       總的來說是這樣的:當View調用invalidate()方法時,會導致整個View樹進行 *       從上至下的一次重繪.比如從最外層的Layout到裡層的Layout,直到每個子View. *       在重繪View樹時ViewGroup和View時按理都會經過onMeasure()和onLayout()以及 *       onDraw()方法.當然系統會判斷這三個方法是否都必須執行,如果沒有必要就不會調用. *       看到這裡就明白了:當這個子View的父容器重繪時,也會調用上面提到的線路: *       onDraw()-->dispatchDraw()-->drawChild()-->child.computeScroll(); *       於是子View(比如此處舉例的ButtonSubClass類)中重寫的computeScroll()方法 *       就會被調用到. *        * 3 View樹的重繪會調用到View中的computeScroll()方法 *  * 4 在computeScroll()方法中 *   在View的源碼中可以看到public void computeScroll(){}是一個空方法. *   具體的實現需要自己來寫.在該方法中我們可調用scrollTo()或scrollBy() *   來實現移動.該方法才是實現移動的核心. *   4.1 利用Scroller的mScroller.computeScrollOffset()判斷移動過程是否完成 *       注意:該方法是Scroller中的方法而不是View中的!!!!!! *       public boolean computeScrollOffset(){ } *       Call this when you want to know the new location. *       If it returns true,the animation is not yet finished.   *       loc will be altered to provide the new location. *       返回true時表示還移動還沒有完成. *   4.2 若動畫沒有結束,則調用:scrollTo(By)(); *       使其滑動scrolling *        * 5 再次調用invalidate(). *   調用invalidate()方法那麼又會重繪View樹. *   從而跳轉到第3步,如此迴圈,直到computeScrollOffset返回false *        *    *    *   具體的滑動過程,請參見示圖 *    *    *  *    *    * 通俗的理解: * 從上可見Scroller執行流程裡面的三個核心方法 * mScroller.startScroll() * mScroller.computeScrollOffset() * view.computeScroll() * 1 在mScroller.startScroll()中為滑動做了一些初始化準備. *   比如:起始座標,滑動的距離和方向以及期間(有預設值)等. *   其實除了這些,在該方法內還做了些其他事情: *   比較重要的一點是設定了動畫開始時間. *  * 2 computeScrollOffset()方法主要是根據當前已經消逝的時間 *   來計算當前的座標點並且儲存在mCurrX和mCurrY值中. *   因為在mScroller.startScroll()中設定了動畫時間,那麼 *   在computeScrollOffset()方法中依據已經消逝的時間就很容易 *   得到當前時刻應該所處的位置並將其儲存在變數mCurrX和mCurrY中. *   除此之外該方法還可判斷動畫是否已經結束. *    *   所以在該樣本中: *   @Override *   public void computeScroll() { *      super.computeScroll(); *      if (mScroller.computeScrollOffset()) { *          scrollTo(mScroller.getCurrX(), 0); *          invalidate(); *      } *   } *   先執行mScroller.computeScrollOffset()判斷了滑動是否結束 *   2.1 返回false,滑動已經結束. *   2.2 返回true,滑動還沒有結束. *       並且在該方法內部也計算了最新的座標值mCurrX和mCurrY. *       就是說在當前時刻應該滑動到哪裡了. *       既然computeScrollOffset()如此貼心,盛情難卻啊! *       於是我們就覆寫View的computeScroll()方法, *       調用scrollTo(By)滑動到那裡!滿足它的一番苦心吧. *    *  * 備忘說明: * 1 樣本沒有做邊界判斷和一些最佳化,在這方面有bug. *   重點是學習Scroller的流程 * 2 不用糾結getCurrX()與getScrollX()有什麼差別,二者得到的值一樣. *   但要注意它們是屬於不同類裡的. *   getCurrX()-------> Scroller.getCurrX() *   getScrollX()-----> View.getScrollX() *  *  * 參考資料: * 0 http://androidxref.com/2.3.6/xref * 1 http://blog.csdn.net/wangjinyu501/article/details/32339379 * 2 http://blog.csdn.net/zjmdp/article/details/7713209 * 3 http://blog.csdn.net/xiaanming/article/details/17483273 *   Thank you very much * */public class ScrollLauncherViewGroup extends ViewGroup {    private int lastX;    private int currentX;    private int distanceX;    private Context mContext;    private Scroller mScroller;public ScrollLauncherViewGroup(Context context) {super(context);mContext=context;mScroller=new Scroller(context);}public ScrollLauncherViewGroup(Context context, AttributeSet attrs) {super(context, attrs);}public ScrollLauncherViewGroup(Context context, AttributeSet attrs,int defStyle) {super(context, attrs, defStyle);}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);}/** * 注意: * 1 getWidth()和getHeight()得到是螢幕的寬和高 *   因為在布局時指定了該控制項的寬和高為fill_parent * 2 view.getScrollX(Y)()得打mScrollX(Y) * 3 調用scrollTo(x, y)後,x和y分別被賦值給mScrollX和mScrollY *   請注意座標方向. */@Overrideprotected void onLayout(boolean arg0, int l, int t, int r, int b) { for (int i = 0; i < getChildCount(); i++) {               View childView = getChildAt(i);               childView.layout(i*getWidth(), 0,getWidth()+ i*getWidth(),getHeight());   }  }@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);}@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:lastX=(int) event.getX();break;case MotionEvent.ACTION_MOVE:currentX=(int) event.getX();distanceX=currentX-lastX;mScroller.startScroll(getScrollX(), 0, -distanceX, 0);break;case MotionEvent.ACTION_UP://手指從螢幕右邊往左滑動,手指抬起時滑動到下一屏if (distanceX<0&&Math.abs(distanceX)>50) {mScroller.startScroll(getScrollX(), 0, getWidth()-(getScrollX()%getWidth()), 0);//手指從螢幕左邊往右滑動,手指抬起時滑動到上一屏} else if (distanceX>0&&Math.abs(distanceX)>50) {mScroller.startScroll(getScrollX(), 0, -(getScrollX()%getWidth()), 0);}break;default:break;}//重繪View樹invalidate(); return true;}@Overridepublic void computeScroll() {super.computeScroll();if (mScroller.computeScrollOffset()) {scrollTo(mScroller.getCurrX(), 0);invalidate();}else{if (mScroller.getCurrX()==getWidth()*(getChildCount()-1)) {Toast.makeText(mContext, "已滑動到最後一屏", Toast.LENGTH_SHORT).show();}}}}




<喎?http://www.bkjia.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+PGJyPgo8L3A+CjxwPjxicj4KPC9wPgo8cD48YnI+CjwvcD4KPHA+PGJyPgo8L3A+CjxwPjxicj4KPC9wPgo8cD48YnI+CjwvcD4KPHA+PGJyPgo8L3A+CjxwPjxicj4KPC9wPgo8cD48YnI+CjwvcD4KPHA+PGJyPgo8L3A+CjxwPjxicj4KPC9wPgo8cD48YnI+CjwvcD4KPHA+PGJyPgo8L3A+CjxwPjxicj4KPC9wPgo8cD48YnI+CjwvcD4KPHA+PGJyPgo8L3A+CjxwPjxicj4KPC9wPgo8cD48YnI+CjwvcD4KPHA+PGJyPgo8L3A+CjxwPjxicj4KPC9wPgo8cD48YnI+CjwvcD4KPHA+PGJyPgo8L3A+CjxwPjxicj4KPC9wPgo8cD48YnI+CjwvcD4KPHA+bWFpbi54bWzI58/COjwvcD4KPHA+PC9wPgo8cHJlIGNsYXNzPQ=="brush:java;">

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.