Android 窗帘(Curtain)效果四之赛贝尔曲线优化

Github源码

下一篇:Android 窗帘(Curtain Menu)效果五之应用场景和使用方法

经过 上一篇文章的优化后我们最终实现的应用效果自我感觉已经不错了。但是仔细再滑动了几下又发下好几处问题,琢磨了下发现这坑有点深啊,正弦曲线优化貌似行不通了;在排除手势拖拽效果的前提下,我们配合如下实际的应用效果图找出问题点:

自己实现特效 Morning Routine
QQ图片20180827232654.gif
Android 窗帘(Curtain)效果四之赛贝尔曲线优化_第1张图片
QQ图片20180827232654.gif

问题点:

(1).效果图的最下边好像被挤出去了,没看到皱褶空隙(上一篇文章已经解决)
(2).左边皱褶的波浪间距是均匀分布的,右边波浪间距是从左往右递减的

这里里我们要对第二点进行优化,纠结了好久,看到这篇文章Android 使用贝塞尔曲线将多点连成一条平滑的曲线灵感就来了,这篇文章的要点主要是三阶赛贝尔曲线的控制点计算,和PathMeasure的使用,这里就不介绍了。我把代码clone下来修改了下实现了一个不均匀分布的波浪曲线,如下图1

Android 窗帘(Curtain)效果四之赛贝尔曲线优化_第2张图片
QQ图片20180908032448.gif

绿色线条上边的红点是我们绘制平滑赛贝尔曲线的顶点,我通过为每个顶点之间设置不同的水平距离和动态控制顶点之间的垂直距离,使得绿色线条从直线扭曲成不均匀波浪赛贝尔曲线,这正是我想要的结果。 附上这个Demo源码

原理先不写,有空再加吧,下面就是我通过赛贝尔曲线对水平/竖直像素优化像素的结果:


Android 窗帘(Curtain)效果四之赛贝尔曲线优化_第3张图片
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;

    //小格相交的总的点数
    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 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;
                //偶数位记录x坐标  奇数位记录Y坐标
                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++) {

                //把每一个水平像素通过正弦公式转换成正弦曲线
                //H_MAX_WAVE_HEIGHT表示波峰跟波低的垂直距离,皱褶后会王桑超过水平线,所以往下偏移WAVE_HEIGHT / 2
                //5表示波浪的密集度,表示波峰波谷总共有五个,对应上面左图的1,2,3,4,5
                //j就是水平像的X轴坐标
                //K决定正弦曲线起始点(x=0)点的Y坐标,k=0就是从波峰波谷的中间开始左->右绘制曲线
                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];

                //垂直方向竖直压缩时的坐标
                float xPostion = origs[(i * (WIDTH + 1) + j) * 2] + (bitmapwidth - origs[(i * (WIDTH + 1) + j) * 2]) * progress;

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

                //垂直方向正弦曲线优化后的坐标,1.1->个波峰波谷
                float vXSinPostion = V_MAX_WAVE_HEIGHT / 2 * progress * (float) Math.sin((float) i / WIDTH * 1.1 * Math.PI + k);
                //每个像素扭曲后的x坐标
                //origs[(i*(WIDTH+1)+j)*2+0] 原图x坐标
                verts[(i * (WIDTH + 1) + j) * 2] = vXSinPostion * ((bitmapwidth - xPostion) / bitmapwidth) + xPostion;
                //每个像素扭曲后的Y坐标
                //origs[(i*(WIDTH+1)+j)*2+1] 原图y坐标
                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)效果五之应用场景和使用方法

你可能感兴趣的:(Android 窗帘(Curtain)效果四之赛贝尔曲线优化)