編輯:Android資訊
上一節我們實現了翻頁的曲線效果,但是效果有點小瑕疵不知道大家發現沒有:
如圖,我們發現折疊區域怪怪的,並沒有實現我們之前的“彎曲”效果,為什麼呢?是計算錯了麼?其實不是的,我們之前測試的時候使用的將canvas填色,但是這裡我們用到的是一張位圖,雖然我們的Path是曲線、Region有曲線區域,但是我們的Bitmap是個規規矩矩的矩形啊,怎麼彎曲~怎麼辦呢?說起扭曲,我們首先想到的是drawBitmapMesh方法,它是我們現在了解的也是唯一的一個能對圖像進行扭曲的API,而使用drawBitmapMesh方法呢我們也可以有多種思路,最簡單的就是最大化恆定細分值,將圖像分割成一定的網格區域,然後判斷離曲線起點和頂點最近的細分線獲取該區域內的細分線交點按指定方向百分比遞減移動起點和頂點的距離值即可,這種方法簡單粗暴,但扭曲不是很精確,正確地說精確度取決於細分,細分也大越精確當然也越耗性能,而第二種方法呢是根據曲線的起點和頂點動態生成細分值,我們可以確保在起點和頂點處都有一條細分線,這樣就可以很准確地計算扭曲范圍,但是我們就需要動態地去不斷計算細分值相當麻煩,用哪種呢?這裡鑒於時間關系還是嘗試用第一種去做,首先定義寬高的細分值:
private static final int SUB_WIDTH = 19, SUB_HEIGHT = 19;// 細分值橫豎各19個網格
19個網格將控件分割為20×20的網格細分線條區域,爾後我們就不需要使用drawBitmap繪制折疊區域了而是改用drawBitmapMesh。之前在講1/6的時候有盆友多次小窗過我離屏緩沖是個什麼意思需要注意什麼,這裡我權當演示,在繪制扭曲圖像的時候使用一個單獨的Bitmap並將其裝載進一個額外的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的大小就剛好和我們的控件一樣大,但是我們實現扭曲的那一部分圖像是超出該范圍外的:
如上圖透明紅色的范圍是我們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方法中生成,每次我們繪制的時候都不再重新生成,也就是說,每次繪制其實都是疊加在上一次的繪制數據上,那麼這就會給我們帶來一個問題,雖然顯示結果有可能不會出錯但是每次繪制都要不斷計算前面的像素次數一多必定會大大影響性能,這時候我們考慮在繪制每一次結果前清空掉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; } } }
雖然上面我們生成了坐標數組,但是並沒有扭曲圖像,在進行下一步操作前我們先來分析一下如何進行扭曲呢,當我們在折疊區域以drawBitmapMesh的方式繪制Bitmap時這時候的圖像實質上是被網格分割了的:
我們的方法其實很簡單,只需要把從短邊長度減短邊長度乘以1/4的位置開始到短邊長度位置的點按遞增向下拽即可對吧:
如上圖所示的兩個藍點分別代表短邊長度減短邊長度乘以1/4的位置和短邊長度位置,因為我們的網格是不變的,但是位置在不斷改變,我們應當獲取離當前位置最近的網格點,比如上圖中的兩個藍點此時我們應該獲取到網格中的對應位置是:
如圖中綠色的藍點,考慮到更好的容差值,我們令起點往後挪一個點而終點往前挪一個點,最終我們的取捨點如下:
同樣,我們右側的也一樣:
那在代碼中的實現也很簡單:
// 計算底部扭曲的起始細分下標 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的點往右“拽”即可實現初步的“扭曲”效果對吧,但是這個拽是有講究的,首先,拽的距離是倍增的,如圖:
每一個點的偏移值相對於上一個點來說是倍增的,倍增多少呢?是基於最大的偏移值來說的,這裡為了簡化一定的問題,我就不去計算了,而是給定一個固定的起始值和倍增率:
// 長邊偏移 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我想項目運行可以看到在我們翻動的過程中扭曲的部分會有一點小跳動,原因很簡單,我們的扭曲只針對了底部最後的一行點y == SUB_HEIGHT和右側最右的一列點x == SUB_WIDTH,而事實上扭曲是個拉扯聯動的效果,扭曲不僅僅會影響最後一行/列同時也會影響倒數第二、三、四行等,只不過這個影響效力是遞減的,這部分就留給大家自己去做了,原理我講的很清楚了。
這一節到此為止,下一節我們將完善最終效果結束本例所有的Study~
源碼下載:傳送門
一 IntentService介紹 IntentService定義的三個基本點:是什麼?怎麼用?如何work? 官方解釋如下: //IntentService定義
自己學了兩三個月的Android,最近花了一周左右的時間寫了個App——Diigoer(已開源),又花了一兩周時間找工作,收到了兩個Offer,也算是對自己學習的
Android 程序中實現Tab類型界面很常見,本人在做項目的時候也經常用到,所以想在這裡總結一下,實現tab類型界面的幾種方式,供大家參考。如有不對之處,歡迎大
一、前言 我很喜歡電腦,可是筆記本還是太大,筆記本電腦再小還是要弄個小包背起來的,智能手機則不同,它完全就是一個手機,可以隨意裝在一個口袋裡隨身攜帶。因此我在20