Android翻頁效果原理實現之模擬扭曲
來源:程序員人生 發布時間:2015-03-02 07:59:38 閱讀次數:3042次
上1節我們實現了翻頁的曲線效果,但是效果有點小瑕疵不知道大家發現沒有:

如圖,我們發現折疊區域怪怪的,并沒有實現我們之前的“曲折”效果,為何呢?是計算錯了么?其實不是的,我們之前測試的時候使用的將canvas填色,但是這里我們用到的是1張位圖,雖然我們的Path是曲線、Region有曲線區域,但是我們的Bitmap是個規規矩矩的矩形啊,怎樣曲折~怎樣辦呢?說起扭曲,我們首先想到的是drawBitmapMesh方法,它是我們現在了解的也是唯1的1個能對圖象進行扭曲的API,而使用drawBitmapMesh方法呢我們也能夠有多種思路,最簡單的就是最大化恒定細分值,將圖象分割成1定的網格區域,然后判斷離曲線出發點和頂點最近的細分線獲得該區域內的細分線交點按指定方向百分比遞減移動出發點和頂點的距離值便可,這類方法簡單粗魯,但扭曲不是很精確,正確地說精確度取決于細分,細分也大越精確固然也越耗性能,而第2種方法呢是根據曲線的出發點和頂點動態生成細分值,我們可以確保在出發點和頂點處都有1條細分線,這樣就能夠很準確地計算扭曲范圍,但是我們就需要動態地去不斷計算細分值相當麻煩,用哪一種呢?這里鑒于時間關系還是嘗試用第1種去做,首先定義寬高的細分值:
private static final int SUB_WIDTH = 19, SUB_HEIGHT = 19;// 細分值橫豎各19個網格
19個網格將控件分割為20x20的網格細分線條區域,爾后我們就不需要使用drawBitmap繪制折疊區域了而是改用drawBitmapMesh。之前在講1/6的時候有盆友屢次小窗過我離屏緩沖是個甚么意思需要注意甚么,這里我權當演示,在繪制扭曲圖象的時候使用1個單獨的Bitmap并將其裝載進1個額外的Canvas中:
private Canvas mCanvasFoldCache;// 履行繪制離屏緩沖的Canvas private Bitmap mBitmapFoldCache;// 存儲繪制離屏緩沖數據的Bitmap
在構造方法中我們實例化mCanvasFoldCache:
/* * 實例化Canvas */ mCanvasFoldCache = new Canvas();
在onSizeChanged中我們生成mBitmapFoldCache:
/* * 生成緩沖位圖并注入Canvas */ mBitmapFoldCache = Bitmap.createBitmap(mViewWidth + 100, mViewHeight + 100, Bitmap.Config.ARGB_8888); mCanvasFoldCache.setBitmap(mBitmapFoldCache);
這里+100的目的是讓Bitmap有過剩的空間繪制扭曲的那部份圖象,我們之前說過Canvas的大小實際取決于內部裝載的Bitmap,如果這里我們不+100,那末mBitmapFoldCache的大小就恰好和我們的控件1樣大,但是我們實現扭曲的那1部份圖象是超越該范圍外的:

如上圖透明紅色的范圍是我們mBitmapFoldCache的大小,但是底部和右邊的扭曲沒有被包括進來,為了彌補這部份的損失我將mBitmapFoldCache的寬高各+100,固然你也能夠計算出具體的值,這里只做演示。
而在繪制時,我們先將所有的數據繪制到mBitmapFoldCache上再將該Bitmap繪制到我們的canvas中:
mCanvasFoldCache.drawBitmapMesh(mBitmaps.get(end - 1), SUB_WIDTH, SUB_HEIGHT, mVerts, 0, null, 0, null); canvas.drawBitmap(mBitmapFoldCache, 0, 0, null);
這里要注意的是,我們的mBitmapFoldCache在onSizeChanged方法中生成,每次我們繪制的時候都不再重新生成,也就是說,每次繪制其實都是疊加在上1次的繪制數據上,那末這就會給我們帶來1個問題,雖然顯示結果有可能不會出錯但是每次繪制都要不斷計算前面的像素次數1多一定會大大影響性能,這時候候我們斟酌在繪制每次結果前清空掉mBitmapFoldCache中的內容:
mCanvasFoldCache.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); mCanvasFoldCache.drawBitmapMesh(mBitmaps.get(end - 1), SUB_WIDTH, SUB_HEIGHT, mVerts, 0, null, 0, null); canvas.drawBitmap(mBitmapFoldCache, 0, 0, null);
題外話到此為止,實際上我們不需要緩沖繪制,直接使用drawBitmapMesh便可:
canvas.drawBitmapMesh(mBitmaps.get(end - 1), SUB_WIDTH, SUB_HEIGHT, mVerts, 0, null, 0, null);
而重點則是我們的這些扭曲點怎樣生成,在構造方法中我們實例化坐標數組:
// 實例化數組并初始化默許數組數據 mVerts = new float[(SUB_WIDTH + 1) * (SUB_HEIGHT + 1) * 2];
在計算了曲線各個點坐標以后我們生成扭曲坐標:
if (sizeLong > mViewHeight) { // 省略大量代碼…… } else { // 省略巨量代碼…… /* * 生成折疊區域的扭曲坐標 */ int index = 0; for (int y = 0; y <= SUB_HEIGHT; y++) { float fy = mViewHeight * y / SUB_HEIGHT; for (int x = 0; x <= SUB_WIDTH; x++) { float fx = mViewWidth * x / SUB_WIDTH; mVerts[index * 2 + 0] = fx; mVerts[index * 2 + 1] = fy; index += 1; } } }
雖然上面我們生成了坐標數組,但是并沒有扭曲圖象,在進行下1步操作前我們先來分析1下如何進行扭曲呢,當我們在折疊區域以drawBitmapMesh的方式繪制Bitmap時這時候候的圖象實質上是被網格分割了的:

我們的方法其實很簡單,只需要把從短邊長度減短邊長度乘以1/4的位置開始到短邊長度位置的點按遞增向下拽便可對吧:

如上圖所示的兩個藍點分別代表短邊長度減短邊長度乘以1/4的位置和短邊長度位置,由于我們的網格是不變的,但是位置在不斷改變,我們應當獲得離當前位置最近的網格點,比如上圖中的兩個藍點此時我們應當獲得到網格中的對應位置是:

如圖中綠色的藍點,斟酌到更好的容差值,我們令出發點往后挪1個點而終點往前挪1個點,終究我們的取舍點以下:

一樣,我們右邊的也1樣:

那在代碼中的實現也很簡單:
// 計算底部扭曲的起始細分下標 mSubWidthStart = Math.round((btmX / mSubMinWidth)) - 1; mSubWidthEnd = Math.round(((btmX + CURVATURE * sizeShort) / mSubMinWidth)) + 1; // 計算右邊扭曲的起始細分下標 mSubHeightStart = (int) (leftY / mSubMinHeight) - 1; mSubHeightEnd = (int) (leftY + CURVATURE * sizeLong / mSubMinHeight) + 1;
我們只需要將mSubWidthStart到mSubWidthEnd之間的點往下“拽”,mSubHeightStart到mSubHeightEnd的點往右“拽”便可實現初步的“扭曲”效果對吧,但是這個拽是有講求的,首先,拽的距離是倍增的,如圖:

每個點的偏移值相對上1個點來講是倍增的,倍增多少呢?是基于最大的偏移值來講的,這里為了簡化1定的問題,我就不去計算了,而是給定1個固定的起始值和倍增率:
// 長邊偏移 float offsetLong = CURVATURE / 2F * sizeLong; // 長邊偏移倍增 float mulOffsetLong = 1.0F; // 短邊偏移 float offsetShort = CURVATURE / 2F * sizeShort; // 短邊偏移倍增 float mulOffsetShort = 1.0F;
這時候候我們可以斟酌開始計算扭曲坐標:
// 計算底部扭曲的起始細分下標 mSubWidthStart = Math.round((btmX / mSubMinWidth)) - 1; mSubWidthEnd = Math.round(((btmX + CURVATURE * sizeShort) / mSubMinWidth)) + 1; // 計算右邊扭曲的起始細分下標 mSubHeightStart = (int) (leftY / mSubMinHeight) - 1; mSubHeightEnd = (int) (leftY + CURVATURE * sizeLong / mSubMinHeight) + 1; /* * 生成折疊區域的扭曲坐標 */ int index = 0; // 長邊偏移 float offsetLong = CURVATURE / 2F * sizeLong; // 長邊偏移倍增 float mulOffsetLong = 1.0F; // 短邊偏移 float offsetShort = CURVATURE / 2F * sizeShort; // 短邊偏移倍增 float mulOffsetShort = 1.0F; for (int y = 0; y <= SUB_HEIGHT; y++) { float fy = mViewHeight * y / SUB_HEIGHT; for (int x = 0; x <= SUB_WIDTH; x++) { float fx = mViewWidth * x / SUB_WIDTH; /* * 右邊扭曲 */ if (x == SUB_WIDTH) { if (y >= mSubHeightStart && y <= mSubHeightEnd) { fx = mViewWidth * x / SUB_WIDTH + offsetLong * mulOffsetLong; mulOffsetLong = mulOffsetLong / 1.5F; } } /* * 底部扭曲 */ if (y == SUB_HEIGHT) { if (x >= mSubWidthStart && x <= mSubWidthEnd) { fy = mViewHeight * y / SUB_HEIGHT + offsetShort * mulOffsetShort; mulOffsetShort = mulOffsetShort / 1.5F; } } mVerts[index * 2 + 0] = fx; mVerts[index * 2 + 1] = fy; index += 1; } }
效果以下:

上面的圖由于上傳大小的限制我緊縮過可能大家看不清楚,如果大家DL我想項目運行可以看到在我們翻動的進程中扭曲的部份會有1點小跳動,緣由很簡單,我們的扭曲只針對了底部最后的1行點y == SUB_HEIGHT和右邊最右的1列點x == SUB_WIDTH,而事實上扭曲是個拉扯聯動的效果,扭曲不單單會影響最后1行/列同時也會影響倒數第2、3、4行等,只不過這個影響效率是遞減的,這部份就留給大家自己去做了,原理我講的很清楚了。
這1節到此為止,下1節我們將完善終究效果結束本例所有的Study~
源碼下載:傳送門
生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈