Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android學習Scroller(五)——詳解Scroller調用過程以及View的重繪

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

編輯:關於Android編程

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;
	@Override
	protected 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);
	}
	
	@Override
	protected 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
	 *   請注意坐標方向.
	 */
	@Override
	protected 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());  
		 }  
	}
	
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
	}
	
	@Override
	public 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;
	}
	
	@Override
	public 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();
			}
		}
	}

}


\



<喎?/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;">

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved