Github源碼
下一篇:Android 窗簾(Curtain)效果二之波浪式動(dòng)態(tài)扭曲效果
寫(xiě)這篇文章的初衷是因?yàn)樵缧r(shí)候看到一款morning routine上的窗簾皺褶效果,自己也想去實(shí)現(xiàn)它,網(wǎng)上也有一些案例但是效果不太好而且沒(méi)有任何的注釋改動(dòng)難度比較,因此想自己的想法去實(shí)現(xiàn)這個(gè)效果。如下圖就是我們最終想模仿實(shí)現(xiàn)的效果:

在開(kāi)始寫(xiě)代碼之前,我們得先了解一些兩個(gè)重要的知識(shí)點(diǎn):
1.Canvas方法drawBitmapMesh的使用:
Canvas提供了一個(gè)方法
drawBitmapMesh(Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors,int colorffset,Paint paint)
這個(gè)方法可以對(duì)bitmap進(jìn)行扭曲,參數(shù)說(shuō)明如下:
bitmap 需要扭曲的源位圖
meshWidth 控制在橫向上把該源位圖劃成成多少格
meshHeight 控制在縱向上把該源位圖劃成成多少格
verts 長(zhǎng)度為(meshWidth + 1) * (meshHeight + 1) * 2的數(shù)組,它記錄了扭曲后的位圖各頂點(diǎn)位置
vertOffset 控制verts數(shù)組中從第幾個(gè)數(shù)組元素開(kāi)始才對(duì)bitmap進(jìn)行扭曲
2.正弦曲線,公式:y=Asin(ωx+φ)+k
正弦曲線可表示為y=Asin(ωx+φ)+k,定義為函數(shù)y=Asin(ωx+φ)+k在直角坐標(biāo)系上的圖象,其中sin為正弦符號(hào),x是直角坐標(biāo)系x軸上的數(shù)值,y是在同一直角坐標(biāo)系上函數(shù)對(duì)應(yīng)的y值,k、ω和φ是常數(shù)(k、ω、φ∈R且ω≠0)
A——振幅,當(dāng)物體作軌跡符合正弦曲線的直線往復(fù)運(yùn)動(dòng)時(shí),其值為行程的1/2。
(ωx+φ)——相位,反映變量y所處的狀態(tài)。
φ——初相,x=0時(shí)的相位;反映在坐標(biāo)系上則為圖像的左右移動(dòng)。
k——偏距,反映在坐標(biāo)系上則為圖像的上移或下移。
ω——角速度, 控制正弦周期(單位弧度內(nèi)震動(dòng)的次數(shù))。
在已經(jīng)大概了解了drawBitmapMesh的使用和正弦曲線的定義后,我們往下就是要去了解兩者之間如何配合使用去扭曲圖片實(shí)現(xiàn)波浪褶皺效果。相對(duì)一張(w * h)像素的圖片來(lái)說(shuō),這張圖片是由h條長(zhǎng)度為w像素的水平直線緊湊排列而成,實(shí)現(xiàn)如下左圖的波浪褶皺效果跟右圖把紅色直線通過(guò)正弦曲線公式扭曲成正弦曲線的原理是一樣的。

左圖的實(shí)現(xiàn)代碼如下:
public class CurtainView extends View {
private Bitmap mbitmap;
private static int WIDTH = 30;
private static int HEIGHT = 30;
/**最大水平的波形高度*/
private float WAVE_HEIGHT = 50;
//小格相交的總的點(diǎn)數(shù)
private int COUNT = (WIDTH + 1) * (HEIGHT + 1);
private float[] verts = new float[COUNT * 2];
private float[] origs = new float[COUNT * 2];
private float k;
private float progress;
public CurtainView(Context context) {
super(context);
init();
}
public CurtainView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CurtainView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
for (int i = 0; i < HEIGHT + 1; i++) {
for (int j = 0; j < WIDTH + 1; j++) {
//把每一個(gè)水平像素通過(guò)正弦公式轉(zhuǎn)換成正弦曲線
//WAVE_HEIGHT表示波峰跟波低的垂直距離,皺褶后會(huì)向上超過(guò)水平線,所以往下偏移WAVE_HEIGHT / 2
//5表示波浪的密集度,表示波峰波谷總共有五個(gè),對(duì)應(yīng)上面左圖的1,2,3,4,5
//j就是水平像的X軸坐標(biāo)
//K決定正弦曲線起始點(diǎn)(x=0)點(diǎn)的Y坐標(biāo),k=0就是從波峰波谷的中間開(kāi)始左->右繪制曲線
float yOffset = WAVE_HEIGHT / 2 + WAVE_HEIGHT / 2 * (float) Math.sin((float) j / WIDTH * 5 * Math.PI + k);
verts[(i * (WIDTH + 1) + j) * 2 + 1] = origs[(i * (WIDTH + 1) + j) * 2 + 1] + yOffset;//
}
}
canvas.drawBitmapMesh(mbitmap, WIDTH, HEIGHT, verts, 0, null, 0, null);
}
int bitmapwidth;
int bitmapheight;
public void init() {
mbitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.timg);
bitmapwidth = mbitmap.getWidth();
bitmapheight = mbitmap.getHeight();
COUNT = (WIDTH + 1) * (HEIGHT + 1);
verts = new float[COUNT * 2];
origs = new float[COUNT * 2];
int index = 0;
for (int i = 0; i < HEIGHT + 1; i++) {
float fy = bitmapheight / (float) HEIGHT * i;
for (int j = 0; j < WIDTH + 1; j++) {
float fx = bitmapwidth / (float) WIDTH * j;
//偶數(shù)位記錄x坐標(biāo) 奇數(shù)位記錄Y坐標(biāo)
origs[index * 2 + 0] = verts[index * 2 + 0] = fx;
origs[index * 2 + 1] = verts[index * 2 + 1] = fy;
index++;
}
}
}
}
關(guān)鍵代碼:
//把每一個(gè)水平像素直線通過(guò)正弦公式轉(zhuǎn)換成正弦曲線
//WAVE_HEIGHT表示波峰跟波谷的垂直距離,皺褶后會(huì)向上超過(guò)水平線,所以往下偏移WAVE_HEIGHT / 2
//5表示波浪的密集度,表示波峰波谷總共有五個(gè),對(duì)應(yīng)上面左圖的1,2,3,4,5
//j就是水平像的X軸坐標(biāo)
//K決定正弦曲線起始點(diǎn)(x=0)點(diǎn)的Y坐標(biāo),k=0就是從波峰波谷的中間開(kāi)始左->右繪制曲線,像右圖一樣
float yOffset = WAVE_HEIGHT / 2 + WAVE_HEIGHT / 2 * (float) Math.sin((float) j / WIDTH * 5 * Math.PI + k);