Android 窗簾(Curtain)效果四之賽貝爾曲線優(yōu)化

Github源碼

下一篇:Android 窗簾(Curtain Menu)效果五之應(yīng)用場(chǎng)景和使用方法

經(jīng)過(guò) 上一篇文章的優(yōu)化后我們最終實(shí)現(xiàn)的應(yīng)用效果自我感覺(jué)已經(jīng)不錯(cuò)了。但是仔細(xì)再滑動(dòng)了幾下又發(fā)下好幾處問(wèn)題,琢磨了下發(fā)現(xiàn)這坑有點(diǎn)深啊,正弦曲線優(yōu)化貌似行不通了;在排除手勢(shì)拖拽效果的前提下,我們配合如下實(shí)際的應(yīng)用效果圖找出問(wèn)題點(diǎn):

自己實(shí)現(xiàn)特效 Morning Routine
QQ圖片20180827232654.gif
QQ圖片20180827232654.gif

問(wèn)題點(diǎn):

(1).效果圖的最下邊好像被擠出去了,沒(méi)看到皺褶空隙(上一篇文章已經(jīng)解決)
(2).左邊皺褶的波浪間距是均勻分布的,右邊波浪間距是從左往右遞減的

這里里我們要對(duì)第二點(diǎn)進(jìn)行優(yōu)化,糾結(jié)了好久,看到這篇文章Android 使用貝塞爾曲線將多點(diǎn)連成一條平滑的曲線靈感就來(lái)了,這篇文章的要點(diǎn)主要是三階賽貝爾曲線的控制點(diǎn)計(jì)算,和PathMeasure的使用,這里就不介紹了。我把代碼clone下來(lái)修改了下實(shí)現(xiàn)了一個(gè)不均勻分布的波浪曲線,如下圖1

QQ圖片20180908032448.gif

綠色線條上邊的紅點(diǎn)是我們繪制平滑賽貝爾曲線的頂點(diǎn),我通過(guò)為每個(gè)頂點(diǎn)之間設(shè)置不同的水平距離和動(dòng)態(tài)控制頂點(diǎn)之間的垂直距離,使得綠色線條從直線扭曲成不均勻波浪賽貝爾曲線,這正是我想要的結(jié)果。附上這個(gè)Demo源碼

原理先不寫(xiě),有空再加吧,下面就是我通過(guò)賽貝爾曲線對(duì)水平/豎直像素優(yōu)化像素的結(jié)果:


QQ圖片20180908035530.gif

完整代碼:

public class CurtainView extends View {
    private Bitmap mbitmap;
    private static int WIDTH = 30;
    private static int HEIGHT = 30;
    //最大垂直的波形高度
    private static float V_MAX_WAVE_HEIGHT = 450;
    //最大垂直的波形高度
    private static float H_MAX_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 int[] colors = new int[COUNT * 2];
    private float k;
    private float progress;

    private int bitmapwidth;
    private int bitmapheight;

    private List<Point> points;
    private float[] pos = new float[2];
    private PathMeasure pathMeasure;

    private float[] xOffsets = new float[WIDTH + 1];
    private float[] yOffsets = new float[WIDTH + 1];

    float[] waveTops = {0, 0.03F, 0.08F, 0.15F, 0.24F, 0.36F, 0.49F, 0.64F, 0.81F, 1.0F};

    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();
    }

    public void init() {
        mbitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.timg);
        bitmapwidth = mbitmap.getWidth();
        bitmapheight = mbitmap.getHeight();

        points = new ArrayList<>();
        pathMeasure = new PathMeasure();

        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] = verts[index * 2] = fx;
                origs[index * 2 + 1] = verts[index * 2 + 1] = fy;
                index++;
            }
        }

        for (int i = 0; i < waveTops.length; i++) {
            Point point = new Point();
            point.x = (int) Math.floor((double) (bitmapwidth * waveTops[i]));
            point.y = i % 2 == 0 ? 0 : (int) (H_MAX_WAVE_HEIGHT * progress);
            points.add(point);
        }

        BezierUtils.measurePath(pathMeasure, points);
    }

    public void setProgress(float progress) {
        this.progress = progress;
        for (int t = 0; t < waveTops.length; t++) {
            Point point = points.get(t);
            point.y = t % 2 == 0 ? 0 : (int) (H_MAX_WAVE_HEIGHT * progress);
        }
        BezierUtils.measurePath(pathMeasure, points);
        invalidate();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        for (int j = 0; j < WIDTH + 1; j++) {
            pathMeasure.getPosTan(pathMeasure.getLength() * j / (float) WIDTH, pos, null);
            xOffsets[j] = pos[0];
            yOffsets[j] = pos[1];
        }

        int index = 0;
        for (int i = 0; i < HEIGHT + 1; i++) {
            for (int j = 0; j < WIDTH + 1; j++) {

                //把每一個(gè)水平像素通過(guò)正弦公式轉(zhuǎn)換成正弦曲線
                //H_MAX_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 = H_MAX_WAVE_HEIGHT / 2 * progress + H_MAX_WAVE_HEIGHT / 2 * progress * (float) Math.sin((float) j / WIDTH * 5 * Math.PI + k);

                yOffset = yOffsets[j];

                //垂直方向豎直壓縮時(shí)的坐標(biāo)
                float xPostion = origs[(i * (WIDTH + 1) + j) * 2] + (bitmapwidth - origs[(i * (WIDTH + 1) + j) * 2]) * progress;

                xPostion = xOffsets[j] + (bitmapwidth - xOffsets[j]) * progress;

                //垂直方向正弦曲線優(yōu)化后的坐標(biāo),1.1->個(gè)波峰波谷
                float vXSinPostion = V_MAX_WAVE_HEIGHT / 2 * progress * (float) Math.sin((float) i / WIDTH * 1.1 * Math.PI + k);
                //每個(gè)像素扭曲后的x坐標(biāo)
                //origs[(i*(WIDTH+1)+j)*2+0] 原圖x坐標(biāo)
                verts[(i * (WIDTH + 1) + j) * 2] = vXSinPostion * ((bitmapwidth - xPostion) / bitmapwidth) + xPostion;
                //每個(gè)像素扭曲后的Y坐標(biāo)
                //origs[(i*(WIDTH+1)+j)*2+1] 原圖y坐標(biāo)
                verts[(i * (WIDTH + 1) + j) * 2 + 1] = origs[(i * (WIDTH + 1) + j) * 2 + 1] + yOffset;//

                int channel = 255 - (int) (yOffset * 3);
                channel = channel < 0 ? 0 : channel;
                channel = channel > 255 ? 255 : channel;
                colors[index] = 0xFF000000 | channel << 16 | channel << 8 | channel;
                index += 1;
            }
        }

        canvas.drawBitmapMesh(mbitmap, WIDTH, HEIGHT, verts, 0, colors, 0, null);
    }
}

下一篇:Android 窗簾(Curtain Menu)效果五之應(yīng)用場(chǎng)景和使用方法

最后編輯于
?著作權(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ù)。

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