多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國(guó)內(nèi)最全I(xiàn)T社區(qū)平臺(tái) 聯(lián)系我們 | 收藏本站
阿里云優(yōu)惠2
您當(dāng)前位置:首頁(yè) > php開源 > 綜合技術(shù) > Android自定義ViewPager(一)――自定義Scroller模擬動(dòng)畫過(guò)程

Android自定義ViewPager(一)――自定義Scroller模擬動(dòng)畫過(guò)程

來(lái)源:程序員人生   發(fā)布時(shí)間:2014-12-10 08:32:22 閱讀次數(shù):2528次

       轉(zhuǎn)載請(qǐng)注明出處:http://blog.csdn.net/allen315410/article/details/41575831

       相信Android SDK提供的ViewPager組件,大家實(shí)在是熟習(xí)不過(guò)了,但是ViewPager存在于support.v4包下的,說(shuō)明ViewPager其實(shí)不存在于初期的android版本中,那末如何在初期的android版本中也一樣使用類似于ViewPager1樣的滑動(dòng)效果呢?這里,我們還是繼續(xù)探討1下andrid的自定義組件好了,并且這篇博文只探討android的1些知識(shí),其實(shí)不是刻意去構(gòu)建1個(gè)自定義的ViewPager去使用,這個(gè)是沒(méi)有必要的,請(qǐng)將注意力集中在實(shí)現(xiàn)這個(gè)效果的知識(shí)點(diǎn)上,方便以后“舉1反3”。


       好了,我們先來(lái)簡(jiǎn)單分析1下ViewPager。ViewPager可以看作是1個(gè)“容器”,在這個(gè)“容器”里可以擺放各種各樣的View類型,例如ViewPager每一個(gè)分頁(yè)上可以放置TextView,ImageView,ListView、GridView等等1系列View組件,實(shí)際上這些View在ViewPager上的擺放我們可以看作是在ViewGroup上Layout各種View(實(shí)際上,這個(gè)實(shí)現(xiàn)是比較復(fù)雜的,這里做個(gè)比喻意義而已),所以我們就能夠抽象理解為,ViewPager相當(dāng)于ViewGroup,并且在這個(gè)ViewGroup上Layout各種View,所以接下來(lái)的代碼中,我們主要需要1個(gè)自定義的ViewGroup來(lái)實(shí)現(xiàn)到達(dá)這樣的效果。另外,還需要在這個(gè)ViewGroup上給每一個(gè)分頁(yè)上的View添加1個(gè)左右滑動(dòng)的效果,以求摹擬出ViewPager上的動(dòng)態(tài)效果。

       關(guān)于自定義ViewGroup的結(jié)構(gòu),我們有必要仔細(xì)探討1下,某些概念還是值得去加深理解的,為了理解方便,請(qǐng)參看下面的“草圖”:


         從上面的草圖可以看到,紅色的邊框代表裝備屏幕,即我們可以用肉眼看見的地方,全部灰色的大邊框代表全部效果,這里稱為“視圖”,每一個(gè)視圖又分為3個(gè)View,這個(gè)3個(gè)或多個(gè)View組成1張很大的視圖。我們要弄清楚,這3者的關(guān)系,裝備屏幕代表的顯示區(qū)域,即我們?cè)谘b備上能看見的范圍,View代表的是單個(gè)的組件,1個(gè)屏幕上可以顯示1個(gè)或多個(gè)View,但是視圖是最容易混淆的東西,視圖理論上是很大的1塊區(qū)域,它不但包括裝備屏幕上能被肉眼看見的1部份,還包括裝備屏幕之外肉眼看不見的地方,就如上圖所示的,子View2和子View3也是視圖的1部份,但是在裝備屏幕以外,就是肉眼看不見的區(qū)域了。視圖里可以寄存很多的View,視圖被用來(lái)管理View的顯示效果。而且,視圖是可以自由活動(dòng)的,通過(guò)控制視圖的活動(dòng),控制視圖在裝備屏幕上的顯示范圍,就能夠切換不同的分頁(yè)了。       


       所以接下來(lái),我們主要去做的就是如何去自定義1個(gè)視圖,如何讓視圖展現(xiàn)不同的View在裝備屏幕上,在Android上管理多個(gè)View的顯示可以通過(guò)自定義的ViewGroup,實(shí)現(xiàn)onLayout給View進(jìn)行排版,初始化排版的時(shí)候,我1共向ViewGroup里添加了6個(gè)子View,這6個(gè)子View呈水平橫向排版,如上圖所示的那樣,每一個(gè)View顯示的寬度和高度跟父View(ViewGroup)相同,首次排版顯現(xiàn)出第1個(gè)子View在屏幕上,其他5個(gè)子View以次添加進(jìn)來(lái),以父View的寬度的N倍數(shù)排版,都被隱藏在裝備屏幕的右側(cè)區(qū)域。下面是自定義ViewGroup的實(shí)現(xiàn)代碼:

package com.example.myviewpager; import android.content.Context; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; public class MyViewPager extends ViewGroup { /** 手勢(shì)辨認(rèn)器 */ private GestureDetector detector; /** 上下文 */ private Context ctx; /** 第1次按下的X軸的坐標(biāo) */ private int firstDownX; /** 記錄當(dāng)前View的id */ private int currId = 0; /** 摹擬動(dòng)畫工具 */ private MyScroller myScroller; public MyViewPager(Context context, AttributeSet attrs) { super(context, attrs); this.ctx = context; init(); } private void init() { myScroller = new MyScroller(ctx); detector = new GestureDetector(ctx, new GestureDetector.OnGestureListener() { @Override public boolean onSingleTapUp(MotionEvent e) { return false; } @Override public void onShowPress(MotionEvent e) { } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // 手指滑動(dòng) scrollBy((int) distanceX, 0); return false; } @Override public void onLongPress(MotionEvent e) { } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { return false; } @Override public boolean onDown(MotionEvent e) { return false; } }); } /** * 對(duì)子View進(jìn)行布局,肯定子View的位置 changed 若為true, * 說(shuō)明布局產(chǎn)生了變化 l  指當(dāng)前View位于父View的位置 */ @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { for (int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); // 指定子View的位置 ,左、上、右、下,是指在ViewGroup坐標(biāo)系中的位置 view.layout(0 + i * getWidth(), 0, getWidth() + i * getWidth(), getHeight()); } } @Override public boolean onTouchEvent(MotionEvent event) { detector.onTouchEvent(event); // 指定手勢(shì)辨認(rèn)器去處理滑動(dòng)事件 // 還是得自己處理1些邏輯 switch (event.getAction()) { case MotionEvent.ACTION_DOWN : // 按下 firstDownX = (int) event.getX(); break; case MotionEvent.ACTION_MOVE : // 移動(dòng) break; case MotionEvent.ACTION_UP : // 抬起 int nextId = 0; // 記錄下1個(gè)View的id if (event.getX() - firstDownX > getWidth() / 2) { // 手指離開點(diǎn)的X軸坐標(biāo)-firstDownX > 屏幕寬度的1半,左移 nextId = (currId - 1) <= 0 ? 0 : currId - 1; } else if (firstDownX - event.getX() > getWidth() / 2) { // 手指離開點(diǎn)的X軸坐標(biāo) - firstDownX < 屏幕寬度的1半,右移 nextId = currId + 1; } else { nextId = currId; } moveToDest(nextId); break; default : break; } return true; } /** * 控制視圖的移動(dòng) * * @param nextId */ private void moveToDest(int nextId) { // nextId的公道范圍是,nextId >=0 && nextId <= getChildCount()⑴ currId = (nextId >= 0) ? nextId : 0; currId = (nextId <= getChildCount() - 1) ? nextId : (getChildCount() - 1); // 視圖移動(dòng),太直接了,沒(méi)有動(dòng)態(tài)進(jìn)程 // scrollTo(currId * getWidth(), 0); // 要移動(dòng)的距離 = 終究的位置 - 現(xiàn)在的位置 int distanceX = currId * getWidth() - getScrollX(); // 設(shè)置運(yùn)行的時(shí)間 myScroller.startScroll(getScrollX(), 0, distanceX, 0); // 刷新視圖 invalidate(); } /** * invalidate();會(huì)致使這個(gè)方法的履行 */ @Override public void computeScroll() { if (myScroller.computeOffset()) { int newX = (int) myScroller.getCurrX(); System.out.println("newX::" + newX); scrollTo(newX, 0); invalidate(); } } }

        1,上面是自定義ViewGroup的所有源碼,接下來(lái)我們漸漸分析1下實(shí)現(xiàn)進(jìn)程,首先是初始化各個(gè)子View的排版,上面已說(shuō)過(guò)了,主要代碼在onLayout()方法中已體現(xiàn),比較簡(jiǎn)單。


        2,實(shí)現(xiàn)手勢(shì)滑動(dòng)效果。盡人皆知,ViewPager可以隨著手指在屏幕上滑動(dòng)而改變不同的分頁(yè),為了實(shí)現(xiàn)一樣的效果,我在自定義ViewGroup中重寫了父類的onTouchEvent(MotionEvent event)方法,該方法被用來(lái)處理滑動(dòng)事件的邏輯。但是為了簡(jiǎn)便起見,我用了手勢(shì)辨認(rèn)器GestureDetector,用這個(gè)手指辨認(rèn)器來(lái)處理手指在屏幕上移動(dòng)時(shí),視圖隨著手指1起移動(dòng)的效果,簡(jiǎn)單在GestureDetector的onScroll()方法中,將移動(dòng)的距離傳遞給ScrollBy(int)作為參數(shù)便可。


        3,處理比較復(fù)雜的手指按下到抬起時(shí),視圖切換。這是1個(gè)具體分析的進(jìn)程,下面是這個(gè)進(jìn)程中觸及的"草圖":


這里,我們以子View2這個(gè)View做示例來(lái)分析1下3種情況:

(1),手指離開點(diǎn)的X軸坐標(biāo) - 手指按下點(diǎn)的X軸坐標(biāo) > 屏幕寬度的1半,左移,屏幕顯示下1個(gè)View

(2),手指離開點(diǎn)的X軸坐標(biāo) - 手指按下點(diǎn)的X軸坐標(biāo) < 屏幕寬度的1半,右移,屏幕顯示上1個(gè)View

(3),以上兩種條件都不滿足,那就停留在當(dāng)前View上,不切換前后View


       4,通過(guò)(3)的進(jìn)程,我們就知道當(dāng)前視圖向哪個(gè)View方向上移動(dòng)了,得到下1個(gè)需要顯示View的id,將這個(gè)id置為當(dāng)前View的id,然后將下1個(gè)需要顯示的View的id*View的寬度,傳遞給ScrollTo(int,0)作為參數(shù),來(lái)控制視圖的移動(dòng)。


       5,通過(guò)以上步驟,View視圖的切換就已完成了,但是有個(gè)問(wèn)題,在View的左右切換時(shí)使用了ScrollTo(int,int)方法,這個(gè)方法將View直接移動(dòng)到指定的位置,但是全部移動(dòng)的進(jìn)程太過(guò)于迅速,1瞬間就完成了View的切換,這樣的體驗(yàn)效果非常差,那末我們?cè)鯓犹嵘w驗(yàn)效果呢?對(duì)了,是在這個(gè)View的切換給1個(gè)慢速的進(jìn)程,讓View切換的進(jìn)程緩慢或勻速的進(jìn)行,這樣體驗(yàn)效果就提生上去了,那末怎樣在切換的進(jìn)程中增加1個(gè)勻速的切換的效果呢?我們無(wú)妨先舉下面1個(gè)小例子,方便理解:


       假設(shè),有個(gè)人小A要走完1個(gè)100米的小路,他自己可以漸漸的走過(guò)去,用時(shí)很多,也能夠1下子跑過(guò)去,用時(shí)極短,但是他想不緊不慢的勻速走完這段小路,該怎樣辦呢?這時(shí)候候他找來(lái)了1位工程師小B,讓工程師小B在旁邊幫他計(jì)算路程,小A在前進(jìn)前詢問(wèn)1下工程師小B,接下來(lái)5秒鐘,我要走多少米?。抗こ處熜就開始計(jì)算出結(jié)果,并且告知小A,你先前進(jìn)10米好了;當(dāng)小A走完這個(gè)10米的路程時(shí),小A又問(wèn)小B,接下來(lái)5秒鐘我要前進(jìn)多少米的距離?小B1頓計(jì)算,告知小A前進(jìn)20米好了,因而小A繼續(xù)前進(jìn)20米,停下來(lái)接著問(wèn)小B......反復(fù)此進(jìn)程,知道小A走完這100米的小路為止。


       上面的例子不難理解吧!因而,在View的切換進(jìn)程中,我們也需要這樣的1位“工程師”時(shí)刻計(jì)算每定時(shí)間間隔內(nèi)的位移,傳遞給View視圖,視圖得到這個(gè)位移,就立馬移動(dòng)到相應(yīng)的位置,再次要求“工程師”計(jì)算下,下1時(shí)間間隔內(nèi)前進(jìn)的位移,以此類推。下面,是我們自定義的1個(gè)計(jì)算位移的工具類源碼:

package com.example.myviewpager; import android.content.Context; import android.os.SystemClock; /** * 計(jì)算視圖偏移的工具類 * * @author Administrator * */ public class MyScroller { /** 開始時(shí)的X坐標(biāo) */ private int startX; /** 開始時(shí)的Y坐標(biāo) */ private int startY; /** X方向上要移動(dòng)的距離 */ private int distanceX; /** Y方向上要移動(dòng)的距離 */ private int distanceY; /** 開始的時(shí)間 */ private long startTime; /** 移動(dòng)是不是結(jié)束 */ private boolean isFinish; /** 當(dāng)前X軸的坐標(biāo) */ private long currX; /** 當(dāng)前Y軸的坐標(biāo) */ private long currY; /** 默許的時(shí)間間隔 */ private int duration = 500; public MyScroller(Context ctx) { } /** * 開始移動(dòng) * * @param startX * 開始時(shí)的X坐標(biāo) * @param startY * 開始時(shí)的Y坐標(biāo) * @param distanceX * X方向上要移動(dòng)的距離 * @param distanceY * Y方向上要移動(dòng)的距離 */ public void startScroll(int startX, int startY, int distanceX, int distanceY) { this.startX = startX; this.startY = startY; this.distanceX = distanceX; this.distanceY = distanceY; this.startTime = SystemClock.uptimeMillis(); this.isFinish = false; } /** * 判斷當(dāng)前運(yùn)行狀態(tài) * * @return */ public boolean computeOffset() { if (isFinish) { return false; } // 取得所用的時(shí)間 long passTime = SystemClock.uptimeMillis() - startTime; System.out.println("passTime::" + passTime); // 如果時(shí)間還在允許的范圍內(nèi) if (passTime < duration) { currX = startX + distanceX * passTime / duration; currY = startY + distanceY * passTime / duration; } else { currX = startX + distanceX; currY = startY + distanceY; isFinish = true; } return true; } /** * 獲得當(dāng)前X的值 * * @return */ public long getCurrX() { return currX; } public void setCurrX(long currX) { this.currX = currX; } /** * 獲得當(dāng)前Y的值 * * @return */ public long getCurrY() { return currY; } public void setCurrY(long currY) { this.currY = currY; } }

分析1下,這個(gè)進(jìn)程。


       當(dāng)我們?cè)谟?jì)算出切換到下1個(gè)View的id時(shí),就能夠得到切換的距離了,公式:要移動(dòng)的距離 = 終究的位置 - 現(xiàn)在的位置;得到這個(gè)移動(dòng)距離以后,拿到這個(gè)距離和初始位置,告知“工程師”――工具類MyScroller,這時(shí)候候可以開始計(jì)算了,初始化代碼以下:

// 要移動(dòng)的距離 = 終究的位置 - 現(xiàn)在的位置 int distanceX = currId * getWidth() - getScrollX(); // 設(shè)置運(yùn)行的時(shí)間 myScroller.startScroll(getScrollX(), 0, distanceX, 0); // 刷新視圖 invalidate();
       初始化完計(jì)算工具類以后,需要刷新當(dāng)前視圖了,調(diào)用invalidate()方法,這個(gè)方法會(huì)經(jīng)過(guò)1系列連鎖反應(yīng),事實(shí)上刷新視圖是個(gè)很復(fù)雜的進(jìn)程,這里不講授了,1直直到觸發(fā)computeScroll()方法,此時(shí),我們需要重寫父類的computeScroll()方法,在這個(gè)方法中,完成自己的1些操作:

/** * invalidate();會(huì)致使這個(gè)方法的履行 */ @Override public void computeScroll() { if (myScroller.computeOffset()) { int newX = (int) myScroller.getCurrX(); System.out.println("newX::" + newX); scrollTo(newX, 0); invalidate(); } }

       

       在這個(gè)方法里,首先調(diào)用1下工具類計(jì)算位移的方法computeOffset()方法,該方法首先判斷1下視圖移動(dòng)是不是完成,若完成返回false,若沒(méi)有完成,先獲得運(yùn)動(dòng)的時(shí)間間隔,如果當(dāng)前運(yùn)動(dòng)的時(shí)間間隔在總時(shí)間間隔duration以內(nèi),那末通過(guò)時(shí)間間隔計(jì)算出這段時(shí)間間隔以后,視圖實(shí)際移動(dòng)到的位置,公式是:開始位置+總的距離/總的時(shí)間*本段移動(dòng)時(shí)間間隔,如果當(dāng)前運(yùn)動(dòng)的時(shí)間間隔超越了總的時(shí)間間隔,那末直接算出最后1次位置,公式:開始位置+移動(dòng)距離。通過(guò)getCurrX得到本次位移的距離,即最新的位移距離,調(diào)用scrollTo(int,int)方法,移動(dòng)視圖到新的位置。最后再次遞歸調(diào)用invalidate()刷新當(dāng)前視圖,然后觸發(fā)computeScroll()方法,繼續(xù)上述步驟,直至超越規(guī)定的時(shí)間間隔,返回false后,視圖的位移進(jìn)程結(jié)束。


      在布局文件中這樣援用:

<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" > <com.example.myviewpager.MyViewPager android:id="@+id/myviewpager" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>
     在MainActivity里需要給這個(gè)自定義的組件初始化幾個(gè)View,為了方便起見,我全部初始化了ImageView,每一個(gè)ImageView設(shè)置不同的背景圖片:

package com.example.myviewpager; import android.os.Bundle; import android.widget.ImageView; import android.app.Activity; public class MainActivity extends Activity { private MyViewPager myViewPager; // 圖片資源 private int[] imageRes = new int[]{R.drawable.a1, R.drawable.a2, R.drawable.a3, R.drawable.a4, R.drawable.a5, R.drawable.a6}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myViewPager = (MyViewPager) findViewById(R.id.myviewpager); ImageView view; for (int i = 0; i < imageRes.length; i++) { view = new ImageView(this); view.setBackgroundResource(imageRes[i]); myViewPager.addView(view); } } }



        另外,在這個(gè)例子程序中我自定義了1個(gè)MyScroller工具類來(lái)計(jì)算位移大小了,感覺費(fèi)時(shí)費(fèi)力,作為學(xué)習(xí)原理可行,但是實(shí)際開發(fā)中,可使用Android為我們提供了類似的、極為簡(jiǎn)便的Helper類,可使用這個(gè)Helper類來(lái)計(jì)算位移,這個(gè)類就是

android.widget.Scroller;

以下是Scroller類的相干方法:  

mScroller.getCurrX()    //獲得mScroller當(dāng)前水平轉(zhuǎn)動(dòng)的位置  
mScroller.getCurrY()    //獲得mScroller當(dāng)前豎直轉(zhuǎn)動(dòng)的位置  
mScroller.getFinalX()   //獲得mScroller終究停止的水平位置  
mScroller.getFinalY()     //獲得mScroller終究停止的豎直位置  
mScroller.setFinalX(int newX)    //設(shè)置mScroller終究停留的水平位置,沒(méi)有動(dòng)畫效果,直接跳到目標(biāo)位置  
mScroller.setFinalY(int newY)    //設(shè)置mScroller終究停留的豎直位置,沒(méi)有動(dòng)畫效果,直接跳到目標(biāo)位置  
mScroller.startScroll(int startX, int startY, int dx, int dy)   //轉(zhuǎn)動(dòng),startX, startY為開始轉(zhuǎn)動(dòng)的位置,dx,dy為轉(zhuǎn)動(dòng)的偏移量  
mScroller.startScroll(int startX, int startY, int dx, int dy, int duration)    //轉(zhuǎn)動(dòng),startX, startY為開始轉(zhuǎn)動(dòng)的位置,dx,dy為轉(zhuǎn)動(dòng)的偏移量, duration為完成轉(zhuǎn)動(dòng)的時(shí)間
mScroller.computeScrollOffset()   //返回值為boolean,true說(shuō)明轉(zhuǎn)動(dòng)還沒(méi)有完成,false說(shuō)明轉(zhuǎn)動(dòng)已完成。這是1個(gè)很重要的方法,通常放在View.computeScroll()中,用來(lái)判斷是不是轉(zhuǎn)動(dòng)是不是結(jié)束。

       Scroller的具體使用實(shí)踐在我的前面博文中有用過(guò),請(qǐng)移步Android自定義控件――側(cè)滑菜單查看相干源碼。


源碼請(qǐng)?jiān)谶@里下載


生活不易,碼農(nóng)辛苦
如果您覺得本網(wǎng)站對(duì)您的學(xué)習(xí)有所幫助,可以手機(jī)掃描二維碼進(jìn)行捐贈(zèng)
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關(guān)閉
程序員人生
主站蜘蛛池模板: 波多野结衣一区在线观看 | 欧美 亚洲 激情 | 久久婷五月天 | 亚洲伊人久久大香线蕉综合图片 | 亚洲福利视频一区 | 羞羞视频免费看网站 | 国美女福利视频午夜精品 | 国产日韩欧美亚洲综合 | 国产大片51精品免费观看 | 男女上下爽无遮挡午夜免费视频 | 牛牛精品国内免费一区 | 精品一区二区三区五区六区 | 色聚网| 黑人巨大xxxx | 亚欧中文字幕 | 亚洲天堂视频网站 | 性色a∨人人爽网站 | 自拍自偷 | 成人男女网免费 | 午夜视频国语 | 日韩 亚洲 欧美 中文 高清 | 91亚洲区国产区精品区 | 中文一区在线观看 | 国产成人高清亚洲一区久久 | 成人a毛片手机免费播放 | 在线看片欧美 | 亚洲欧美日韩第一页 | 国产毛片一级国语版 | 性欧美vide0另类hd | 欧美日韩不卡码一区二区三区 | 日本午夜视频 | 亚洲第九十七页 | 一区二区三区高清在线观看 | 免费在线播放视频 | 精品毛片视频 | 日韩中文字幕视频在线 | 欧美亚洲一区 | 亚洲日本在线免费观看 | 中文字幕日本一本二本三区 | 亚洲国产系列 | 中文字幕在线不卡精品视频99 |