Android魔法(第三弹)—— 一步步实现对折页面
1、效果展示
實(shí)現(xiàn)后的效果如下2、AnimationListView框架解讀
1)框架產(chǎn)生原因
由于有幾個(gè)效果處理手法類似,可以看成一個(gè)系列,所以整理了一些公共的接口和類,本篇文章會(huì)仔細(xì)介紹一下,故篇幅很能會(huì)較長一些,可能也會(huì)枯燥一點(diǎn)。 首先,我們不僅僅要實(shí)現(xiàn)對(duì)折的效果,實(shí)際上整體可以看成是一個(gè)特殊的ViewPager,每個(gè)Item都占滿屏幕,而且切換Item時(shí)是對(duì)折效果。生活中更貼近的例子應(yīng)該是掛歷,一頁頁的上翻下翻。 所以對(duì)折效果是切換時(shí)的過渡效果,我們首先要實(shí)現(xiàn)這種ViewPager —— AnimationListView,然后再添加上效果。 AnimationListView這個(gè)類代碼較多,這里就不整個(gè)貼出來了,大家可以去項(xiàng)目源碼中查看,這里只將關(guān)鍵部分代碼講解一下。2)實(shí)現(xiàn)頁面緩存
AnimationListView很多思想類似ViewPager,使用了Adapter來加載每個(gè)頁面,并且緩存了三個(gè)頁面:當(dāng)前頁面、上個(gè)頁面和下個(gè)頁面,這樣提前緩存可以讓頁面表現(xiàn)的更流暢。這部分代碼如下: /***?設(shè)置adapter,設(shè)置監(jiān)聽并重新布局頁面*?@param?adapter*/ public?void?setAdapter(Adapter?adapter)?{mAdapter?=?adapter;mAdapter.registerDataSetObserver(new?DataSetObserver()?{@Overridepublic?void?onChanged()?{super.onChanged();refreshByAdapter();}@Overridepublic?void?onInvalidated()?{super.onInvalidated();refreshByAdapter();}});mCurrentPosition?=?0;refreshByAdapter(); }/***?重新布局頁面*?先添加mCacheItems,再添加mFolioView。這樣mFolioView一直處于頂端,不會(huì)被遮擋。*/ private?void?refreshByAdapter()?{removeAllViews();if?(mCurrentPosition?<?0)?{mCurrentPosition?=?0;}if?(mCurrentPosition?>=?mAdapter.getCount())?{mCurrentPosition?=?mAdapter.getCount()?-?1;}//如果緩存item不夠3個(gè),用第一個(gè)item添補(bǔ)while(mCacheItems.size()?<?3){View?item?=?mAdapter.getView(0,?null,?null);addView(item,?mLayoutParams);mCacheItems.add(item);}//刷新緩存item的數(shù)據(jù)。for?(int?i?=?0;?i?<?mCacheItems.size();?i++)?{int?index?=?mCurrentPosition?+?i?-?1;View?item?=?mCacheItems.get(i);//當(dāng)在列表頂部或底部,會(huì)有一個(gè)緩存Item不刷新,因?yàn)楫?dāng)前位置沒有上一個(gè)或下一個(gè)位置if?(index?>=?0?&&?index?<?mAdapter.getCount())?{item?=?mAdapter.getView(index,?item,?null);}}//刷新界面initItemVisible();//添加翻轉(zhuǎn)處理的viewsetAnimationViewVisible(false); }/***?下一頁*/ protected?void?pageNext()?{setAnimationViewVisible(false);//當(dāng)前位置加1mCurrentPosition++;if?(mCurrentPosition?>=?mAdapter.getCount())?{mCurrentPosition?=?mAdapter.getCount()?-?1;}//移出緩存的第一個(gè)item,并且刷新成當(dāng)前位置的下一位,并添加到緩存列表最后View?first?=?mCacheItems.remove(0);if?(mCurrentPosition?+?1?<?mAdapter.getCount())?{first?=?mAdapter.getView(mCurrentPosition?+?1,?first,?null);}mCacheItems.add(first);//刷新界面initItemVisible(); }/***?上一頁*/ protected?void?pagePrevious()?{//當(dāng)前位置減1mCurrentPosition--;if?(mCurrentPosition?<?0)?{mCurrentPosition?=?0;}//移出緩存的最后一個(gè)item,并且刷新成當(dāng)前位置的上一位,并添加到緩存列表開始View?last?=?mCacheItems.remove(mCacheItems.size()?-?1);if?(mCurrentPosition?-?1?>=?0)?{last?=?mAdapter.getView(mCurrentPosition?-?1,?last,?null);}mCacheItems.add(0,?last);//刷新界面initItemVisible();setAnimationViewVisible(false); }/***?刷新所有的item,并且只顯示當(dāng)前位置即中間的item*/ private?void?initItemVisible()?{for?(int?i?=?0;?i?<?mCacheItems.size();?i++)?{View?item?=?mCacheItems.get(i);item.invalidate();if?(item?==?null)?{continue;}if?(i?==?1)?{item.setVisibility(VISIBLE);}?else?{item.setVisibility(INVISIBLE);}} }首先,我們來看refreshByAdpter這個(gè)函數(shù),可以看到當(dāng)adapter的數(shù)據(jù)有變化時(shí)都會(huì)調(diào)用這個(gè)函數(shù),它的作用就是根據(jù)當(dāng)前的position初始化頁面使adpter生效。
在這個(gè)函數(shù)中,根據(jù)當(dāng)前的position中adapter中獲取了三個(gè)(或者兩個(gè),當(dāng)處于開始或最后時(shí))view緩存起來,并且緩存的三個(gè)view都添加到了頁面上。至于為甚么將三個(gè)view都添加到頁面中,而不是只添加當(dāng)前頁面,是因?yàn)楹竺鎸?shí)現(xiàn)切換效果需要,這個(gè)后面會(huì)解釋到。
當(dāng)三個(gè)view都添加進(jìn)頁面,可以看到又調(diào)用了initItemVisible函數(shù),通過代碼可以看到這個(gè)函數(shù)主要就是處理三個(gè)view的展示。將當(dāng)前頁面設(shè)為VISIBLE,而其他頁面設(shè)為INVISIBLE,保證了當(dāng)前頁面的展示。
最后調(diào)用了setAnimationViewVisible函數(shù),這個(gè)函數(shù)用于展示隱藏處理切換動(dòng)畫的view,后面會(huì)講到。
然后,pageNext和pagePrevious這兩個(gè)方法類似,分別實(shí)現(xiàn)向上和向下切頁(不包含切換動(dòng)畫)。以pageNext為例,取出緩存mCacheItems的第一個(gè)view,為這個(gè)view重新裝載再下一頁的數(shù)據(jù),然后添加回mCacheItems尾部,調(diào)用initItemVisible重置顯示。這樣就顯示了下一頁內(nèi)容,同時(shí)也緩存了再下一頁的內(nèi)容。
3)處理touch事件
Ok,下面我們來研究一個(gè)切換時(shí)的操作。 由于這個(gè)切換不僅僅是一個(gè)動(dòng)畫,整個(gè)效果實(shí)際上是跟著手指滑動(dòng)而改變的,所以需要處理touch事件,代碼如下: @Override public?boolean?onTouchEvent(MotionEvent?event)?{if?(getWidth()?<=?0?||?getHeight()?<=?0)?{return?false;}//當(dāng)動(dòng)畫組件動(dòng)畫執(zhí)行中,則忽略touch事件if(mAnimationView?!=?null?&&?mAnimationView.isAnimationRunning()){return?true;}switch?(event.getAction())?{case?MotionEvent.ACTION_DOWN:mTmpX?=?event.getX();mTmpY?=?event.getY();break;case?MotionEvent.ACTION_MOVE:/***?計(jì)算移動(dòng)的距離*?這里加了判斷,是為了防止mMoveX或mMoveY為0,因?yàn)楹竺鏁?huì)根據(jù)這倆個(gè)判斷移動(dòng)方向。*/if?(event.getX()?!=?mTmpX)?{mMoveX?=?event.getX()?-?mTmpX;}if?(event.getY()?!=?mTmpY)?{mMoveY?=?event.getY()?-?mTmpY;}//創(chuàng)建動(dòng)畫組件createAnimationView();/***?計(jì)算當(dāng)前的位置百分比*?0則代表初始位置*?0.x則代表下一頁翻轉(zhuǎn)的百分比*?1則代表翻到了下一頁。*?-0.x則代表上一頁翻轉(zhuǎn)的百分比*?-1則代表翻到上一頁。*/float?percent?=?mAnimationView.getAnimationPercent();if?(isVertical)?{percent?+=?mMoveY?/?getHeight();}?else?{percent?+=?mMoveX?/?getWidth();}//保證位置在1到-1之間if(percent?<?-1){percent?=?-1;}else?if(percent?>?1){percent?=?1;}if(canPage(mMoveX,?mMoveY,?percent))?{//如果動(dòng)畫組件未展示將其展示if?(!isAnimationViewVisible())?{setAnimationViewVisible(true);}//裝載或切換動(dòng)畫的圖片switchAniamtionBitmap(percent);mAnimationView.setAnimationPercent(percent,?event,?isVertical);}mTmpX?=?event.getX();mTmpY?=?event.getY();break;case?MotionEvent.ACTION_UP:case?MotionEvent.ACTION_CANCEL:case?MotionEvent.ACTION_OUTSIDE:/***?計(jì)算移動(dòng)的距離*?這里加了判斷,是為了防止mMoveX或mMoveY為0,因?yàn)楹竺鏁?huì)根據(jù)這倆個(gè)判斷移動(dòng)方向。*/if?(event.getX()?!=?mTmpX)?{mMoveX?=?event.getX()?-?mTmpX;}if?(event.getY()?!=?mTmpY)?{mMoveY?=?event.getY()?-?mTmpY;}/***?計(jì)算結(jié)束位置百分比*?0則代表初始位置*?1則代表翻到了下一頁。*?-1則代表翻到上一頁。*/float?toPercent?=?0;if?(isVertical)?{toPercent?=?mMoveY?>?0???1?:?0;}?else?{toPercent?=?mMoveX?>?0???1?:?0;}if(mAnimationView.getAnimationPercent()?<?0){//如果是翻上一頁的狀態(tài),則起點(diǎn)終點(diǎn)應(yīng)該是0和-1toPercent?-=?1;}//如果可以翻頁,則播放翻頁動(dòng)畫if(canPage(mMoveX,?mMoveY,?toPercent))?{mAnimationView.startAnimation(isVertical,?event,?toPercent);}mMoveX?=?0;mMoveY?=?0;break;}return?true; }這部分是AnimationListView的核心。
首先分析ACTION_MOVE這個(gè)狀態(tài)。可以看到最開始調(diào)用了createAnimationView這個(gè)函數(shù),代碼如下:
private?void?createAnimationView(){if(mAnimationView?==?null){try?{Constructor<??extends?AnimationViewInterface>?constructor?=?animationClass.getConstructor(Context.class);mAnimationView?=?constructor.newInstance(getContext());}?catch?(Exception?e)?{e.printStackTrace();}}mAnimationView.setOnAnimationViewListener(new?OnAnimationViewListener()?{@Overridepublic?void?pageNext()?{AnimationListView.this.pageNext();}@Overridepublic?void?pagePrevious()?{AnimationListView.this.pagePrevious();}}); }mAnimationView是一個(gè)AnimationViewInterface接口的實(shí)現(xiàn),主要是用于處理和展示切換的動(dòng)效的。我們這次實(shí)現(xiàn)的對(duì)折只是其中一種效果而已,對(duì)于這個(gè)接口和實(shí)現(xiàn),我們后面來講,暫時(shí)大家知道這是一個(gè)用于展示動(dòng)效的View就可以了。
由于AnimationViewInterface有多個(gè)子類的實(shí)現(xiàn),所以這里使用一種工廠模式,即使用反射根據(jù)animationClass來初始化。
回到ACTION_MOVE的代碼,創(chuàng)建成功后先根據(jù)滑動(dòng)的方向判斷是向上還是向下翻頁,并通過移動(dòng)的距離計(jì)算出一個(gè)百分比。然后通過一個(gè)canPage函數(shù)判斷是否可以翻頁,這個(gè)函數(shù)比較簡單,主要就是判斷是否到開始或結(jié)尾了。如何canPage為true,可以看到依次調(diào)用了三個(gè)函數(shù):setAnimationViewVisible,switchAnimationBitmap和mAnimationView.setAnimationPercent。
首先看setAnimationViewVisible這個(gè)函數(shù):
protected?void?setAnimationViewVisible(boolean?visible)?{if(mAnimationView?==?null){return;}if?(visible)?{addView((View)?mAnimationView,?mLayoutParams);}?else?{removeView((View)?mAnimationView);} }上面也提到過這個(gè)函數(shù),通過代碼可以看到就是根據(jù)visible將一個(gè)mAnimationView添加或移除來達(dá)到展示隱藏的效果。
調(diào)用這個(gè)函數(shù)就是將mAnimationView添加到屏幕上,并且處于最頂層,覆蓋了當(dāng)前頁面。
然后是switchAnimationBitmap函數(shù):
private?void?switchAniamtionBitmap(float?percent){//如果當(dāng)前為初始狀態(tài)即未翻轉(zhuǎn),或轉(zhuǎn)變了翻轉(zhuǎn)方向則需切換背景圖if(mAnimationView.getAnimationPercent()?==?0||?mAnimationView.getAnimationPercent()?*?percent?<?0)?{//前景圖是當(dāng)前頁面,即緩存頁面中的第二個(gè)Bitmap?frontBitmap?=?getViewBitmap(mCacheItems.get(1));Bitmap?backBitmap?=?null;/***?背景圖根據(jù)翻轉(zhuǎn)方向不同改變。*?如果要翻到上一頁,則背景圖為緩存頁面中的第一個(gè)*?如果要翻到下一頁,則背景圖為緩存頁面中的第二個(gè)*/if?(isVertical)?{backBitmap?=?getViewBitmap(mCacheItems.get(mMoveY?>?0???0?:?2));}?else?{backBitmap?=?getViewBitmap(mCacheItems.get(mMoveX?>?0???0?:?2));}//初始化動(dòng)畫組件initAniamtionView(frontBitmap,?backBitmap);} }根據(jù)翻頁方向的不同,分別對(duì)當(dāng)前頁面和即將翻到的頁面進(jìn)行截屏,即getViewBitmap函數(shù)。這就是前面為什么要將三個(gè)緩存的Item都添加到布局中的原因,因?yàn)橹挥刑砑拥狡聊簧喜拍軐?nèi)容截屏出來。至于為什么要截屏,因?yàn)槊總€(gè)Item的布局可能復(fù)雜,而在對(duì)折這個(gè)效果中,我們需要將一個(gè)頁面分成兩部分單獨(dú)處理效果,這樣直接對(duì)Item操作幾乎不可能。所以我們截屏后對(duì)Bitmap處理可操作性大很多,這也是為什么mAnimationView一定要在最頂層覆蓋其他View的原因。實(shí)際上,當(dāng)我們進(jìn)行翻頁時(shí)看到的是mAnimationView,而真正的頁面都隱藏在下面。
至于getViewBitmap中如何實(shí)現(xiàn)截屏,代碼很簡單,大家看源碼就好了。
取得兩個(gè)頁面的截屏設(shè)置到mAnimationView中,至于怎么處理這兩個(gè)bitmap就在mAnimationView中了,而且有這兩個(gè)Bitmap我們可以實(shí)現(xiàn)很多很多效果,這也是為什么花這么大篇幅來講解AnimationListView這個(gè)類的原因,因?yàn)橐院笪覀兪褂眠@個(gè)類來實(shí)現(xiàn)很多不同的效果。
最后是mAnimationView.setAnimationPercent,通過之前計(jì)算出來的百分比來設(shè)置這一瞬間的效果展示。這個(gè)函數(shù)不同的子類實(shí)現(xiàn)不同,后面再說。
整個(gè)ACTION_MOVE過程,根據(jù)移動(dòng)來實(shí)時(shí)的改變展示。當(dāng)滑動(dòng)完成時(shí),由于可能翻頁效果只展示到中間某一點(diǎn),所以需要啟動(dòng)一個(gè)動(dòng)畫來實(shí)現(xiàn)剩下的效果完成整個(gè)翻頁,這就是ACTION_UP狀態(tài)中代碼的作用。
這樣AnimationListView這個(gè)類主要的功能就解析完成了,主要是實(shí)現(xiàn)一個(gè)類似ViewPager的View,并且重點(diǎn)處理用戶的touch事件。
3、AnimationViewInterface接口
下面我們來真正的實(shí)現(xiàn)對(duì)折效果FolioView,首先FolioView要實(shí)現(xiàn)AnimationViewInterface這個(gè)接口,這個(gè)接口代碼如下: public?interface?AnimationViewInterface?{/***?初始化圖片*?@param?frontBitmap?前景圖片*?@param?backBitmap? ?背景圖片*/void?setBitmap(Bitmap?frontBitmap,?Bitmap?backBitmap);boolean?isAnimationRunning();/***?開啟動(dòng)畫*?從當(dāng)前狀態(tài)到toPercent的狀態(tài)*?@param?isVertical*?@param?event*?@param?toPercent?動(dòng)畫的最終位置百分比*/void?startAnimation(boolean?isVertical,?MotionEvent?event,?float?toPercent);float?getAnimationPercent();/***?設(shè)置動(dòng)畫到某一幀的狀態(tài)*?用于滑動(dòng)過程中實(shí)時(shí)改變animationview的狀態(tài)*?@param?percent?當(dāng)前處于動(dòng)畫的位置百分比*?@param?event*?@param?isVertical*/void?setAnimationPercent(float?percent,?MotionEvent?event,?boolean?isVertical);void?setDuration(long?duration);void?setOnAnimationViewListener(OnAnimationViewListener?onAnimationViewListener); }至于這些方法的作用,通過之前的講解基本上都能猜出來了,就不細(xì)說了。通過實(shí)現(xiàn)這個(gè)接口,我們不僅僅可以實(shí)現(xiàn)對(duì)折效果,實(shí)際上由于setBitmap我們得到了兩個(gè)bitmap,我們可以利用這兩個(gè)bitmap實(shí)現(xiàn)任何想要的效果。在下一篇文章中,我會(huì)利用AnimationListView和AnimationViewInterface實(shí)現(xiàn)一個(gè)百葉窗的效果。
4、對(duì)折動(dòng)畫分析
如何實(shí)現(xiàn)對(duì)折效果?其實(shí)整個(gè)對(duì)折的效果中分為三個(gè)區(qū)域,如圖 其中區(qū)域1繪制處于前端的頁面的上部分,區(qū)域2則繪制處于后端頁面的下部分,并且這兩個(gè)區(qū)域是不會(huì)做任何改變的。 而區(qū)域3較復(fù)雜,也是這個(gè)效果的關(guān)鍵,如果處于下半部分則繪制前端頁面的下半部分,處于上半部分則繪制后端頁面的上半部分,并且做了梯形變形實(shí)現(xiàn)近大遠(yuǎn)小的效果。區(qū)別如圖: 這一就產(chǎn)生了折頁的效果,而且區(qū)域3需要移動(dòng)并改變梯形大小來實(shí)現(xiàn)移動(dòng)的效果和動(dòng)畫。 其實(shí)還有一個(gè)區(qū)域,即陰影區(qū)域,其位置根據(jù)區(qū)域3的位置而改變,并且陰影的透明度也要隨著改變。5、對(duì)折效果繪制FolioView.onDraw
繪制代碼如下: @Override protected void onDraw(Canvas canvas) {if (mFrontBitmapTop == null || mBackBitmapTop == null) {return;}if(getHeight() <= 0){return;}/*** 計(jì)算翻轉(zhuǎn)的比率* 用于計(jì)算圖片的拉伸和陰影效果*/float rate;if (mFolioY >= getHeight() / 2) {rate = (float) (getHeight() - mFolioY) * 2 / getHeight();} else {rate = (float) mFolioY * 2 / getHeight();}/*** 根據(jù)上翻下翻判斷上下的圖片*/Bitmap topBitmap = null;Bitmap bottomBitmap = null;Bitmap topBitmapFolie = null;Bitmap bottomBitmapFolie = null;if(mCurrentPercent < 0){topBitmap = mFrontBitmapTop;bottomBitmap = mBackBitmapBottom;topBitmapFolie = mFrontBitmapBottom;bottomBitmapFolie = mBackBitmapTop;}else if(mCurrentPercent > 0){topBitmap = mBackBitmapTop;bottomBitmap = mFrontBitmapBottom;topBitmapFolie = mBackBitmapBottom;bottomBitmapFolie = mFrontBitmapTop ;}if (topBitmap == null || bottomBitmap == null) {return;}/*** 在上半部分繪制topBitmap*/Rect topHoldSrc = new Rect(0, 0, topBitmap.getWidth(), topBitmap.getHeight());Rect topHoldDst = new Rect(0, 0, getWidth(), getHeight() / 2);canvas.drawBitmap(topBitmap, topHoldSrc, topHoldDst, null);/*** 在下半部分繪制bottomBitmap*/Rect bottomHoldSrc = new Rect(0, 0, bottomBitmap.getWidth(), bottomBitmap.getHeight());Rect bottomHoldDst = new Rect(0, getHeight() / 2, getWidth(), getHeight());canvas.drawBitmap(bottomBitmap, bottomHoldSrc, bottomHoldDst, null);/*** 繪制陰影* 陰影與翻轉(zhuǎn)是在同一區(qū)域,并且根據(jù)翻轉(zhuǎn)程度改變*/Paint shadowP = new Paint();shadowP.setColor(0xff000000);shadowP.setAlpha((int) ((1 - rate) * FOLIO_SHADOW_ALPHA));if (mFolioY >= getHeight() / 2) {canvas.drawRect(bottomHoldDst, shadowP);} else {canvas.drawRect(topHoldDst, shadowP);}/*** 繪制翻轉(zhuǎn)效果的圖片* 翻轉(zhuǎn)圖片是一個(gè)梯形,根據(jù)情況梯形大小位置等不相同*/mFolioBitmap = null;float[] folioSrc = null;float[] folioDst = null;if (mFolioY >= getHeight() / 2) {//當(dāng)翻轉(zhuǎn)位置在中部偏下時(shí),取topBitmapFolie,同時(shí)繪制區(qū)域?yàn)橐粋€(gè)正梯形mFolioBitmap = topBitmapFolie;folioDst = new float[]{0, getHeight() / 2,getWidth(), getHeight() / 2,rate * FOLIO_SCALE * getWidth() + getWidth(), mFolioY,-rate * FOLIO_SCALE * getWidth(), mFolioY};} else {//當(dāng)翻轉(zhuǎn)位置在中部偏上時(shí),取bottomBitmapFolie,同時(shí)繪制區(qū)域?yàn)橐粋€(gè)倒梯形mFolioBitmap = bottomBitmapFolie;folioDst = new float[]{-rate * FOLIO_SCALE * getWidth(), mFolioY,rate * FOLIO_SCALE * getWidth() + getWidth(), mFolioY,getWidth(), getHeight() / 2,0, getHeight() / 2};}folioSrc = new float[]{0, 0,mFolioBitmap.getWidth(), 0,mFolioBitmap.getWidth(), mFolioBitmap.getHeight(),0, mFolioBitmap.getHeight()};Matrix matrix = new Matrix();matrix.setPolyToPoly(folioSrc, 0, folioDst, 0, folioSrc.length >> 1);canvas.drawBitmap(mFolioBitmap, matrix, null);super.onDraw(canvas); }可以看到mFolioY這個(gè)參數(shù)是關(guān)鍵,這個(gè)參數(shù)是是指區(qū)域3梯形長邊到頁面頂端的距離。通過這個(gè)參數(shù)來計(jì)算區(qū)域3的位置、陰影的大小和梯形的形狀等等。
在繪制過程中,首先繪制區(qū)域1和區(qū)域2,因?yàn)檫@兩個(gè)區(qū)域固定不變而且不受其他參數(shù)影響。
然后根據(jù)mFolioY判斷區(qū)域3是在上半部分還是下半部分。先繪制陰影,陰影區(qū)域是與區(qū)域3在同一部分,采用簡單的方法,完全覆蓋區(qū)域1或區(qū)域2即可。
然后再去繪制區(qū)域3,這樣可以覆蓋陰影部分。通過判斷區(qū)域3的位置選用不同的圖片,并且使用Matrix和矩陣將圖片做梯形變形,然后繪制到指定的區(qū)域。
這就是整個(gè)繪制的過程,當(dāng)我們改變mFolioY這個(gè)參數(shù)并且重繪頁面時(shí)就可以產(chǎn)生移動(dòng)的效果了。
6、對(duì)折動(dòng)畫解析
通過之前的分析我們知道,整個(gè)移動(dòng)過程實(shí)際上有兩個(gè)階段:手動(dòng)和自動(dòng)。手動(dòng)階段跟隨touch的move事件而移動(dòng),當(dāng)touch結(jié)束的時(shí)候則進(jìn)行自動(dòng)動(dòng)畫。1)手動(dòng)階段
主要是調(diào)用AnimationViewInterface的setAnimationPrecent函數(shù)來實(shí)現(xiàn)移動(dòng),這個(gè)函數(shù)代碼如下: public?void?setAnimationPercent(float?percent,?MotionEvent?event,?boolean?isVertical)?{if(!isVertical){return;}if(getHeight()?<=?0){return;}/***?計(jì)算翻轉(zhuǎn)的位置*?如果位置超出了區(qū)域,則完成翻轉(zhuǎn)*/mFolioY?=?percent?>?0???percent?*?getHeight()?:?(1?+?percent)?*?getHeight();invalidate();mCurrentPercent?=?percent; }可以看到主要就是通過percent計(jì)算出mFolioY,然后重繪。
2)自動(dòng)階段
調(diào)用另外一個(gè)函數(shù):startAnmation,如下 public?void?startAnimation(boolean?isVertical,?MotionEvent?event,?final?float?toPercent)?{if(!isVertical){return;}if(getHeight()?<=?0){return;}/***?播放翻轉(zhuǎn)動(dòng)畫*?先計(jì)算動(dòng)畫結(jié)束的位置,然后設(shè)定動(dòng)畫從當(dāng)前位置翻到結(jié)束點(diǎn)*?動(dòng)畫的實(shí)質(zhì)上是不停改變翻轉(zhuǎn)位置并重繪*/float?endPosition?=?0;if?(mCurrentPercent?<?0)?{endPosition?=?toPercent?==?0???getHeight()?:?0;}?else{endPosition?=?toPercent?==?0???0?:?getHeight();}mFolioAnimation?=?ObjectAnimator.ofFloat(this,?"folioY",?endPosition);mFolioAnimation.setDuration((long)(mduration?*?Math.abs(toPercent?-?mCurrentPercent)));mFolioAnimation.addListener(new?Animator.AnimatorListener()?{@Overridepublic?void?onAnimationStart(Animator?animation)?{}@Overridepublic?void?onAnimationEnd(Animator?animation)?{mCurrentPercent?=?0;if(mOnAnimationViewListener?!=?null){if(toPercent?==?1){mOnAnimationViewListener.pagePrevious();}else?if(toPercent?==?-1){mOnAnimationViewListener.pageNext();}}}@Overridepublic?void?onAnimationCancel(Animator?animation)?{}@Overridepublic?void?onAnimationRepeat(Animator?animation)?{}});mFolioAnimation.start(); }先通過toPercent計(jì)算endPosition,這個(gè)參數(shù)是動(dòng)畫結(jié)束時(shí)mFolioY的值。
然后啟動(dòng)一個(gè)屬性動(dòng)畫,通過setter和getter將mFolioY的值從當(dāng)前值逐漸改變至endPosition。當(dāng)動(dòng)畫結(jié)束時(shí)判斷翻頁方向并調(diào)用listener的對(duì)應(yīng)方法實(shí)現(xiàn)頁面的切換。
7、總結(jié)
總結(jié)一下,對(duì)折這個(gè)效果其實(shí)不難,無論繪制還是屬性動(dòng)畫,都使用的比較簡單。本篇文章更主要的是介紹這樣一個(gè)框架,在這個(gè)框架的基礎(chǔ)上,我們之后要實(shí)現(xiàn)一些更復(fù)雜的效果,比如下一篇的百葉窗效果。源碼:
關(guān)注公眾號(hào):BennuCTech,發(fā)送“FastWidget”獲取完整源碼總結(jié)
以上是生活随笔為你收集整理的Android魔法(第三弹)—— 一步步实现对折页面的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 减小TabLayout高度而不影响每个t
- 下一篇: Android魔法(第四弹)—— 一步步