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

