Android 窗簾(Curtain Menu)效果一之波浪式扭曲圖片

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)的效果:

QQ圖片20180826235810.gif

在開(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ò)正弦曲線公式扭曲成正弦曲線的原理是一樣的。


1233.png

左圖的實(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);

Android 窗簾(Curtain)效果二之波浪式動(dòng)態(tài)扭曲效果

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容