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



PS:html

該篇博客已經deprecated,再也不維護。詳情請參見 java

站在源代碼的肩膀上全解Scroller工做機制android

 http://blog.csdn.net/lfdfhl/article/details/53143114canvas


MainActivity例如如下:app

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()方法中根據已經消逝的時間就很是easy * 獲得當前時刻應該所處的位置並將其保存在變量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() * * */ 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(); } } } } ide








main.xml例如如下:佈局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <cc.ww.ScrollLauncherViewGroup
        android:id="@+id/scrollLauncherViewGroup"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
    />

</RelativeLayout>


PS:post

該篇博客已經deprecated。再也不維護,詳情請參見 學習

站在源代碼的肩膀上全解Scroller工做機制優化

http://blog.csdn.net/lfdfhl/article/details/53143114

相關文章
相關標籤/搜索