Android 吸入动画效果实现分解

Android 吸入动画效果详解 . 
 
这里,我要介绍的是如何在Android上面实现一个类似的效果。先看看我实现的效果图。 
 
上图演示了动画的某几帧,其中从1 - 4,演示了图片从原始图形吸入到一个点(红色标识)。 
实现这样的效果,我们利用了Canvas.drawBitmapMesh()方法,这里涉及到了一个Mesh的概念。 
2,Mesh的概念 
Mesh表示网格,说得通俗一点,可以将画板想像成一张格子布,在这个张布上绘制图片。对于一个网格端点均匀分布的网格来说,横向有meshWidth + 1个顶点,纵向有meshHeight + 1个端点。顶点数据verts是以行优先的数组(二维数组以一维数组表示,先行后列)。网格可以不均匀分布。请看下图所示: 
 
上图中显示了把图片分成很多格子,上图中的每个格子是均匀的,它的顶点数是:(meshWidth + 1) * (meshHeight + 1)个,那么放这些顶点的一维数据的大小应该是:(meshWidth + 1) * (meshHeight + 1) * 2 (一个点包含x, y坐标) 
复制代码代码如下:

float[] vertices = new float[:(meshWidth + 1) * (meshHeight + 1) * 2]; 

试想,我们让这个格子(mesh)不均匀分布,那么绘制出来的图片就会变形,请看下图所示: 
 
3,如何构建Mesh 
吸入动画的核心是吸入到一个点,那么我们就是要在不同的时刻构造出不同的mesh的顶点坐标,我们是怎么做的呢? 
3.1,创建两条路径(Path) 
假如我们的吸入效果是从上到下吸入,我们构造的Path是如下图所示: 
 
上图中蓝色的线表示我们构造的Path,其实只要我们沿着这两条Path来构造mesh顶点就可以了。 
构建两条Path的代码如下: 
复制代码代码如下:

mFirstPathMeasure.setPath(mFirstPath, false); 
mSecondPathMeasure.setPath(mSecondPath, false); 
float w = mBmpWidth; 
float h = mBmpHeight; 
mFirstPath.reset(); 
mSecondPath.reset(); 
mFirstPath.moveTo(0, 0); 
mSecondPath.moveTo(w, 0); 
mFirstPath.lineTo(0, h); 
mSecondPath.lineTo(w, h); 
mFirstPath.quadTo(0, (endY + h) / 2, endX, endY); 
mSecondPath.quadTo(w, (endY + h) / 2, endX, endY); 

3.2,根据Path来计算顶点坐标 
算法: 
1,假如我们把格子分为WIDTH, HEIGHT份,把Path的长度分的20份,[0, length],表示20个时刻。 
2,第0时间,我们要的形状是一个矩形,第1时刻可能是梯形,第n时间可能是一个三角形。下图说明了动画过程中图片的变化。 
 
3,第一条(左)Path的长度为len1,第二条(右)Path的长度为len2,对于任意时刻 t [0 - 20],我们可以知道梯形的四个顶点距Path最顶端的length。 
左上角:t * (len1 / 20) 
左下角:t * (len1 / 20) + bitmapHeight 
右上角:t * (len2 / 20) 
右下角:t * (len2 / 20) + bitmapHeight 
 
我们可以通过PathMeasure类根据length算出在Path上面点的坐标,也就是说,根据两条Path,我们可以分别算了四个顶点的坐标,我这里分别叫做A, B, C, D(顺时针方向),有了点的坐标,我们可以算出AD,BC的长度,并且将基进行HEIGHT等分(因为我们把mesh分成宽WIDTH,高HEIGHT等分),将AD,BC上面每等分的点连接起来形成一条直接,将再这条直接水平WIDTH等分,根据直线方程,依据x算出y,从而算出每一个顶点的坐标。(请参考上图) 
下面是计算顶点坐标的详细代码: 
复制代码代码如下:

private void buildMeshByPathOnVertical(int timeIndex) 

mFirstPathMeasure.setPath(mFirstPath, false); 
mSecondPathMeasure.setPath(mSecondPath, false); 
int index = 0; 
float[] pos1 = {0.0f, 0.0f}; 
float[] pos2 = {0.0f, 0.0f}; 
float firstLen = mFirstPathMeasure.getLength(); 
float secondLen = mSecondPathMeasure.getLength(); 
float len1 = firstLen / HEIGHT; 
float len2 = secondLen / HEIGHT; 
float firstPointDist = timeIndex * len1; 
float secondPointDist = timeIndex * len2; 
float height = mBmpHeight; 
mFirstPathMeasure.getPosTan(firstPointDist, pos1, null); 
mFirstPathMeasure.getPosTan(firstPointDist + height, pos2, null); 
float x1 = pos1[0]; 
float x2 = pos2[0]; 
float y1 = pos1[1]; 
float y2 = pos2[1]; 
float FIRST_DIST = (float)Math.sqrt( (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) ); 
float FIRST_H = FIRST_DIST / HEIGHT; 
mSecondPathMeasure.getPosTan(secondPointDist, pos1, null); 
mSecondPathMeasure.getPosTan(secondPointDist + height, pos2, null); 
x1 = pos1[0]; 
x2 = pos2[0]; 
y1 = pos1[1]; 
y2 = pos2[1]; 
float SECOND_DIST = (float)Math.sqrt( (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) ); 
float SECOND_H = SECOND_DIST / HEIGHT; 
for (int y = 0; y <= HEIGHT; ++y) 

mFirstPathMeasure.getPosTan(y * FIRST_H + firstPointDist, pos1, null); 
mSecondPathMeasure.getPosTan(y * SECOND_H + secondPointDist, pos2, null); 
float w = pos2[0] - pos1[0]; 
float fx1 = pos1[0]; 
float fx2 = pos2[0]; 
float fy1 = pos1[1]; 
float fy2 = pos2[1]; 
float dy = fy2 - fy1; 
float dx = fx2 - fx1; 
for (int x = 0; x <= WIDTH; ++x) 

// y = x * dy / dx 
float fx = x * w / WIDTH; 
float fy = fx * dy / dx; 
mVerts[index * 2 + 0] = fx + fx1; 
mVerts[index * 2 + 1] = fy + fy1; 
index += 1; 




4,如何绘制 
绘制代码很简单,调用Canvas.drawBitmapMesh方法。最本质是要计算出一个顶点数组。 
复制代码代码如下:

canvas.drawBitmapMesh(mBitmap, 
mInhaleMesh.getWidth(), 
mInhaleMesh.getHeight(), 
mInhaleMesh.getVertices(), 
0, null, 0, mPaint); 

5,如何实现动画 
复制代码代码如下:

protected void applyTransformation(float interpolatedTime, Transformation t) 

int curIndex = 0; 
Interpolator interpolator = this.getInterpolator(); 
if (null != interpolator) 

float value = interpolator.getInterpolation(interpolatedTime); 
interpolatedTime = value; 

if (mReverse) 

interpolatedTime = 1.0f - interpolatedTime; 

curIndex = (int)(mFromIndex + (mEndIndex - mFromIndex) * interpolatedTime); 
if (null != mListener) 

mListener.onAnimUpdate(curIndex); 



在动画里面,我们计算出要做动画的帧的index,假设我们把吸入动画分为20帧,在动画里面,计算出每一帧,最后通过onAnimUpdate(int index)方法回调,在这个方法实现里面,我们根据帧的index来重新计算一个新的mesh顶点数组,再用这个数组来绘制bitmap。这样,我们就可以看来一组连续变化的mesh,也就能看到吸扩效果的动画。 
动画类里面,最核心就是扩展Animation类,重写applyTransformation方法。 
6,总结 
本文简单介绍了吸放效果的实现,根据这个原理,我们可以构造更加复杂的Path来做更多的效果。同时,也能实现向上,向左,向右的吸入效果。 
最本质是我们要理解Mesh的概念,最核心的工作就是构造出Mesh的顶点坐标。 
计算Mesh通常是一个很复杂的工作,作一些简单的变形还可以,对于太复杂的变形,可能还是不太方便。另外,像书籍翻页的效果,用mesh其实也是可以做到的。只是算法复杂一点。 
这里不能给出完整的代码,原理可能不是说得太清楚,但愿给想实现的人一个思路指引吧。 
7,实现代码 
InhaleAnimationActivity.java 
复制代码代码如下:

package com.nj1s.lib.test.anim; 
import android.os.Bundle; 
import android.view.Gravity; 
import android.view.Menu; 
import android.view.MenuItem; 
import android.view.View; 
import android.widget.Button; 
import android.widget.LinearLayout; 
import com.nj1s.lib.mesh.InhaleMesh.InhaleDir; 
import com.nj1s.lib.test.GABaseActivity; 
import com.nj1s.lib.test.R; 
import com.nj1s.lib.test.effect.BitmapMesh; 
public class InhaleAnimationActivity extends GABaseActivity 

private static final boolean DEBUG_MODE = false; 
private BitmapMesh.SampleView mSampleView = null; 
@Override 
protected void onCreate(Bundle savedInstanceState) 

super.onCreate(savedInstanceState); 
LinearLayout linearLayout = new LinearLayout(this); 
mSampleView = new BitmapMesh.SampleView(this); 
mSampleView.setIsDebug(DEBUG_MODE); 
mSampleView.setLayoutParams(new LinearLayout.LayoutParams(-1, -1)); 
Button btn = new Button(this); 
btn.setText("Run"); 
btn.setTextSize(20.0f); 
btn.setLayoutParams(new LinearLayout.LayoutParams(150, -2)); 
btn.setOnClickListener(new View.OnClickListener() 

boolean mReverse = false; 
@Override 
public void onClick(View v) 

if (mSampleView.startAnimation(mReverse)) 

mReverse = !mReverse; 


}); 
linearLayout.setOrientation(LinearLayout.VERTICAL); 
linearLayout.setGravity(Gravity.CENTER_VERTICAL); 
linearLayout.addView(btn); 
linearLayout.addView(mSampleView); 
setContentView(linearLayout); 

@Override 
public boolean onCreateOptionsMenu(Menu menu) 

getMenuInflater().inflate(R.menu.inhale_anim_menu, menu); 
return true; 

@Override 
public boolean onOptionsItemSelected(MenuItem item) 

switch(item.getItemId()) 

case R.id.menu_inhale_down: 
mSampleView.setInhaleDir(InhaleDir.DOWN); 
break; 
case R.id.menu_inhale_up: 
mSampleView.setInhaleDir(InhaleDir.UP); 
break; 
case R.id.menu_inhale_left: 
mSampleView.setInhaleDir(InhaleDir.LEFT); 
break; 
case R.id.menu_inhale_right: 
mSampleView.setInhaleDir(InhaleDir.RIGHT); 
break; 

return super.onOptionsItemSelected(item); 



BitmapMesh.java 
复制代码代码如下:

/* 
* Copyright (C) 2008 The Android Open Source Project 

* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 

* http://www.apache.org/licenses/LICENSE-2.0 

* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
*/ 
package com.nj1s.lib.test.effect; 
import android.content.Context; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.graphics.Canvas; 
import android.graphics.Color; 
import android.graphics.Matrix; 
import android.graphics.Paint; 
import android.graphics.Paint.Style; 
import android.graphics.Path; 
import android.util.Log; 
import android.view.MotionEvent; 
import android.view.View; 
import android.view.animation.Animation; 
import android.view.animation.Interpolator; 
import android.view.animation.Transformation; 
import com.nj1s.lib.mesh.InhaleMesh; 
import com.nj1s.lib.mesh.InhaleMesh.InhaleDir; 
import com.nj1s.lib.test.R; 
public class BitmapMesh { 
public static class SampleView extends View { 
private static final int WIDTH = 40; 
private static final int HEIGHT = 40; 
private final Bitmap mBitmap; 
private final Matrix mMatrix = new Matrix(); 
private final Matrix mInverse = new Matrix(); 
private boolean mIsDebug = false; 
private Paint mPaint = new Paint(); 
private float[] mInhalePt = new float[] {0, 0}; 
private InhaleMesh mInhaleMesh = null; 
public SampleView(Context context) { 
super(context); 
setFocusable(true); 
mBitmap = BitmapFactory.decodeResource(getResources(), 
R.drawable.beach); 
mInhaleMesh = new InhaleMesh(WIDTH, HEIGHT); 
mInhaleMesh.setBitmapSize(mBitmap.getWidth(), mBitmap.getHeight()); 
mInhaleMesh.setInhaleDir(InhaleDir.DOWN); 

public void setIsDebug(boolean isDebug) 

mIsDebug = isDebug; 

public void setInhaleDir(InhaleMesh.InhaleDir dir) 

mInhaleMesh.setInhaleDir(dir); 
float w = mBitmap.getWidth(); 
float h = mBitmap.getHeight(); 
float endX = 0; 
float endY = 0; 
float dx = 10; 
float dy = 10; 
mMatrix.reset(); 
switch (dir) 

case DOWN: 
endX = w / 2; 
endY = getHeight() - 20; 
break; 
case UP: 
dy = getHeight() - h - 20; 
endX = w / 2; 
endY = -dy + 10; 
break; 
case LEFT: 
dx = getWidth() - w - 20; 
endX = -dx + 10; 
endY = h / 2; 
break; 
case RIGHT: 
endX = getWidth() - 20; 
endY = h / 2; 
break; 

mMatrix.setTranslate(dx, dy); 
mMatrix.invert(mInverse); 
buildPaths(endX, endY); 
buildMesh(w, h); 
invalidate(); 

@Override 
protected void onSizeChanged(int w, int h, int oldw, int oldh) 

super.onSizeChanged(w, h, oldw, oldh); 
float bmpW = mBitmap.getWidth(); 
float bmpH = mBitmap.getHeight(); 
mMatrix.setTranslate(10, 10); 
//mMatrix.setTranslate(10, 10); 
mMatrix.invert(mInverse); 
mPaint.setColor(Color.RED); 
mPaint.setStrokeWidth(2); 
mPaint.setAntiAlias(true); 
buildPaths(bmpW / 2, h - 20); 
buildMesh(bmpW, bmpH); 

public boolean startAnimation(boolean reverse) 

Animation anim = this.getAnimation(); 
if (null != anim && !anim.hasEnded()) 

return false; 

PathAnimation animation = new PathAnimation(0, HEIGHT + 1, reverse, 
new PathAnimation.IAnimationUpdateListener() 

@Override 
public void onAnimUpdate(int index) 

mInhaleMesh.buildMeshes(index); 
invalidate(); 

}); 
if (null != animation) 

animation.setDuration(1000); 
this.startAnimation(animation); 

return true; 

@Override 
protected void onDraw(Canvas canvas) 

Log.i("leehong2", "onDraw =========== "); 
canvas.drawColor(0xFFCCCCCC); 
canvas.concat(mMatrix); 
canvas.drawBitmapMesh(mBitmap, 
mInhaleMesh.getWidth(), 
mInhaleMesh.getHeight(), 
mInhaleMesh.getVertices(), 
0, null, 0, mPaint); 
// =========================================== 
// Draw the target point. 
mPaint.setColor(Color.RED); 
mPaint.setStyle(Style.FILL); 
canvas.drawCircle(mInhalePt[0], mInhalePt[1], 5, mPaint); 
if (mIsDebug) 

// =========================================== 
// Draw the mesh vertices. 
canvas.drawPoints(mInhaleMesh.getVertices(), mPaint); 
// =========================================== 
// Draw the paths 
mPaint.setColor(Color.BLUE); 
mPaint.setStyle(Style.STROKE); 
Path[] paths = mInhaleMesh.getPaths(); 
for (Path path : paths) 

canvas.drawPath(path, mPaint); 



private void buildMesh(float w, float h) 

mInhaleMesh.buildMeshes(w, h); 

private void buildPaths(float endX, float endY) 

mInhalePt[0] = endX; 
mInhalePt[1] = endY; 
mInhaleMesh.buildPaths(endX, endY); 

int mLastWarpX = 0; 
int mLastWarpY = 0; 
@Override 
public boolean onTouchEvent(MotionEvent event) 

float[] pt = { event.getX(), event.getY() }; 
mInverse.mapPoints(pt); 
if (event.getAction() == MotionEvent.ACTION_UP) 

int x = (int)pt[0]; 
int y = (int)pt[1]; 
if (mLastWarpX != x || mLastWarpY != y) { 
mLastWarpX = x; 
mLastWarpY = y; 
buildPaths(pt[0], pt[1]); 
invalidate(); 


return true; 


private static class PathAnimation extends Animation 

public interface IAnimationUpdateListener 

public void onAnimUpdate(int index); 

private int mFromIndex = 0; 
private int mEndIndex = 0; 
private boolean mReverse = false; 
private IAnimationUpdateListener mListener = null; 
public PathAnimation(int fromIndex, int endIndex, boolean reverse, IAnimationUpdateListener listener) 

mFromIndex = fromIndex; 
mEndIndex = endIndex; 
mReverse = reverse; 
mListener = listener; 

public boolean getTransformation(long currentTime, Transformation outTransformation) { 
boolean more = super.getTransformation(currentTime, outTransformation); 
Log.d("leehong2", "getTransformation more = " + more); 
return more; 

@Override 
protected void applyTransformation(float interpolatedTime, Transformation t) 

int curIndex = 0; 
Interpolator interpolator = this.getInterpolator(); 
if (null != interpolator) 

float value = interpolator.getInterpolation(interpolatedTime); 
interpolatedTime = value; 

if (mReverse) 

interpolatedTime = 1.0f - interpolatedTime; 

curIndex = (int)(mFromIndex + (mEndIndex - mFromIndex) * interpolatedTime); 
if (null != mListener) 

Log.i("leehong2", "onAnimUpdate =========== curIndex = " + curIndex); 
mListener.onAnimUpdate(curIndex); 





最核心的类 
InhaleMesh 
复制代码代码如下:

package com.nj1s.lib.mesh; 
import android.graphics.Path; 
import android.graphics.PathMeasure; 
public class InhaleMesh extends Mesh 

public enum InhaleDir 

UP, 
DOWN, 
LEFT, 
RIGHT, 

private Path mFirstPath = new Path(); 
private Path mSecondPath = new Path(); 
private PathMeasure mFirstPathMeasure = new PathMeasure(); 
private PathMeasure mSecondPathMeasure = new PathMeasure(); 
private InhaleDir mInhaleDir = InhaleDir.DOWN; 
public InhaleMesh(int width, int height) 

super(width, height); 

public void setInhaleDir(InhaleDir inhaleDir) 

mInhaleDir = inhaleDir; 

public InhaleDir getInhaleDir() 

return mInhaleDir; 

@Override 
public void buildPaths(float endX, float endY) 

if (mBmpWidth <= 0 || mBmpHeight <= 0) 

throw new IllegalArgumentException( 
"Bitmap size must be > 0, do you call setBitmapSize(int, int) method?"); 

switch (mInhaleDir) 

case UP: 
buildPathsUp(endX, endY); 
break; 
case DOWN: 
buildPathsDown(endX, endY); 
break; 
case RIGHT: 
buildPathsRight(endX, endY); 
break; 
case LEFT: 
buildPathsLeft(endX, endY); 
break; 


@Override 
public void buildMeshes(int index) 

if (mBmpWidth <= 0 || mBmpHeight <= 0) 

throw new IllegalArgumentException( 
"Bitmap size must be > 0, do you call setBitmapSize(int, int) method?"); 

switch (mInhaleDir) 

case UP: 
case DOWN: 
buildMeshByPathOnVertical(index); 
break; 
case RIGHT: 
case LEFT: 
buildMeshByPathOnHorizontal(index); 
break; 


public Path[] getPaths() 

return new Path[] { mFirstPath, mSecondPath }; 

private void buildPathsDown(float endX, float endY) 

mFirstPathMeasure.setPath(mFirstPath, false); 
mSecondPathMeasure.setPath(mSecondPath, false); 
float w = mBmpWidth; 
float h = mBmpHeight; 
mFirstPath.reset(); 
mSecondPath.reset(); 
mFirstPath.moveTo(0, 0); 
mSecondPath.moveTo(w, 0); 
mFirstPath.lineTo(0, h); 
mSecondPath.lineTo(w, h); 
mFirstPath.quadTo(0, (endY + h) / 2, endX, endY); 
mSecondPath.quadTo(w, (endY + h) / 2, endX, endY); 

private void buildPathsUp(float endX, float endY) 

mFirstPathMeasure.setPath(mFirstPath, false); 
mSecondPathMeasure.setPath(mSecondPath, false); 
float w = mBmpWidth; 
float h = mBmpHeight; 
mFirstPath.reset(); 
mSecondPath.reset(); 
mFirstPath.moveTo(0, h); 
mSecondPath.moveTo(w, h); 
mFirstPath.lineTo(0, 0); 
mSecondPath.lineTo(w, 0); 
mFirstPath.quadTo(0, (endY - h) / 2, endX, endY); 
mSecondPath.quadTo(w, (endY - h) / 2, endX, endY); 

private void buildPathsRight(float endX, float endY) 

mFirstPathMeasure.setPath(mFirstPath, false); 
mSecondPathMeasure.setPath(mSecondPath, false); 
float w = mBmpWidth; 
float h = mBmpHeight; 
mFirstPath.reset(); 
mSecondPath.reset(); 
mFirstPath.moveTo(0, 0); 
mSecondPath.moveTo(0, h); 
mFirstPath.lineTo(w, 0); 
mSecondPath.lineTo(w, h); 
mFirstPath.quadTo((endX + w) / 2, 0, endX, endY); 
mSecondPath.quadTo((endX + w) / 2, h, endX, endY); 

private void buildPathsLeft(float endX, float endY) 

mFirstPathMeasure.setPath(mFirstPath, false); 
mSecondPathMeasure.setPath(mSecondPath, false); 
float w = mBmpWidth; 
float h = mBmpHeight; 
mFirstPath.reset(); 
mSecondPath.reset(); 
mFirstPath.moveTo(w, 0); 
mSecondPath.moveTo(w, h); 
mFirstPath.lineTo(0, 0); 
mSecondPath.lineTo(0, h); 
mFirstPath.quadTo((endX - w) / 2, 0, endX, endY); 
mSecondPath.quadTo((endX - w) / 2, h, endX, endY); 

private void buildMeshByPathOnVertical(int timeIndex) 

mFirstPathMeasure.setPath(mFirstPath, false); 
mSecondPathMeasure.setPath(mSecondPath, false); 
int index = 0; 
float[] pos1 = {0.0f, 0.0f}; 
float[] pos2 = {0.0f, 0.0f}; 
float firstLen = mFirstPathMeasure.getLength(); 
float secondLen = mSecondPathMeasure.getLength(); 
float len1 = firstLen / HEIGHT; 
float len2 = secondLen / HEIGHT; 
float firstPointDist = timeIndex * len1; 
float secondPointDist = timeIndex * len2; 
float height = mBmpHeight; 
mFirstPathMeasure.getPosTan(firstPointDist, pos1, null); 
mFirstPathMeasure.getPosTan(firstPointDist + height, pos2, null); 
float x1 = pos1[0]; 
float x2 = pos2[0]; 
float y1 = pos1[1]; 
float y2 = pos2[1]; 
float FIRST_DIST = (float)Math.sqrt( (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) ); 
float FIRST_H = FIRST_DIST / HEIGHT; 
mSecondPathMeasure.getPosTan(secondPointDist, pos1, null); 
mSecondPathMeasure.getPosTan(secondPointDist + height, pos2, null); 
x1 = pos1[0]; 
x2 = pos2[0]; 
y1 = pos1[1]; 
y2 = pos2[1]; 
float SECOND_DIST = (float)Math.sqrt( (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) ); 
float SECOND_H = SECOND_DIST / HEIGHT; 
if (mInhaleDir == InhaleDir.DOWN) 

for (int y = 0; y <= HEIGHT; ++y) 

mFirstPathMeasure.getPosTan(y * FIRST_H + firstPointDist, pos1, null); 
mSecondPathMeasure.getPosTan(y * SECOND_H + secondPointDist, pos2, null); 
float w = pos2[0] - pos1[0]; 
float fx1 = pos1[0]; 
float fx2 = pos2[0]; 
float fy1 = pos1[1]; 
float fy2 = pos2[1]; 
float dy = fy2 - fy1; 
float dx = fx2 - fx1; 
for (int x = 0; x <= WIDTH; ++x) 

// y = x * dy / dx 
float fx = x * w / WIDTH; 
float fy = fx * dy / dx; 
mVerts[index * 2 + 0] = fx + fx1; 
mVerts[index * 2 + 1] = fy + fy1; 
index += 1; 



else if (mInhaleDir == InhaleDir.UP) 

for (int y = HEIGHT; y >= 0; --y) 

mFirstPathMeasure.getPosTan(y * FIRST_H + firstPointDist, pos1, null); 
mSecondPathMeasure.getPosTan(y * SECOND_H + secondPointDist, pos2, null); 
float w = pos2[0] - pos1[0]; 
float fx1 = pos1[0]; 
float fx2 = pos2[0]; 
float fy1 = pos1[1]; 
float fy2 = pos2[1]; 
float dy = fy2 - fy1; 
float dx = fx2 - fx1; 
for (int x = 0; x <= WIDTH; ++x) 

// y = x * dy / dx 
float fx = x * w / WIDTH; 
float fy = fx * dy / dx; 
mVerts[index * 2 + 0] = fx + fx1; 
mVerts[index * 2 + 1] = fy + fy1; 
index += 1; 




private void buildMeshByPathOnHorizontal(int timeIndex) 

mFirstPathMeasure.setPath(mFirstPath, false); 
mSecondPathMeasure.setPath(mSecondPath, false); 
int index = 0; 
float[] pos1 = {0.0f, 0.0f}; 
float[] pos2 = {0.0f, 0.0f}; 
float firstLen = mFirstPathMeasure.getLength(); 
float secondLen = mSecondPathMeasure.getLength(); 
float len1 = firstLen / WIDTH; 
float len2 = secondLen / WIDTH; 
float firstPointDist = timeIndex * len1; 
float secondPointDist = timeIndex * len2; 
float width = mBmpWidth; 
mFirstPathMeasure.getPosTan(firstPointDist, pos1, null); 
mFirstPathMeasure.getPosTan(firstPointDist + width, pos2, null); 
float x1 = pos1[0]; 
float x2 = pos2[0]; 
float y1 = pos1[1]; 
float y2 = pos2[1]; 
float FIRST_DIST = (float)Math.sqrt( (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) ); 
float FIRST_X = FIRST_DIST / WIDTH; 
mSecondPathMeasure.getPosTan(secondPointDist, pos1, null); 
mSecondPathMeasure.getPosTan(secondPointDist + width, pos2, null); 
x1 = pos1[0]; 
x2 = pos2[0]; 
y1 = pos1[1]; 
y2 = pos2[1]; 
float SECOND_DIST = (float)Math.sqrt( (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) ); 
float SECOND_X = SECOND_DIST / WIDTH; 
if (mInhaleDir == InhaleDir.RIGHT) 

for (int x = 0; x <= WIDTH; ++x) 

mFirstPathMeasure.getPosTan(x * FIRST_X + firstPointDist, pos1, null); 
mSecondPathMeasure.getPosTan(x * SECOND_X + secondPointDist, pos2, null); 
float h = pos2[1] - pos1[1]; 
float fx1 = pos1[0]; 
float fx2 = pos2[0]; 
float fy1 = pos1[1]; 
float fy2 = pos2[1]; 
float dy = fy2 - fy1; 
float dx = fx2 - fx1; 
for (int y = 0; y <= HEIGHT; ++y) 

// x = y * dx / dy 
float fy = y * h / HEIGHT; 
float fx = fy * dx / dy; 
index = y * (WIDTH + 1) + x; 
mVerts[index * 2 + 0] = fx + fx1; 
mVerts[index * 2 + 1] = fy + fy1; 



else if (mInhaleDir == InhaleDir.LEFT) 

for (int x = WIDTH; x >= 0; --x) 
//for (int x = 0; x <= WIDTH; ++x) 

mFirstPathMeasure.getPosTan(x * FIRST_X + firstPointDist, pos1, null); 
mSecondPathMeasure.getPosTan(x * SECOND_X + secondPointDist, pos2, null); 
float h = pos2[1] - pos1[1]; 
float fx1 = pos1[0]; 
float fx2 = pos2[0]; 
float fy1 = pos1[1]; 
float fy2 = pos2[1]; 
float dy = fy2 - fy1; 
float dx = fx2 - fx1; 
for (int y = 0; y <= HEIGHT; ++y) 

// x = y * dx / dy 
float fy = y * h / HEIGHT; 
float fx = fy * dx / dy; 
index = y * (WIDTH + 1) + WIDTH - x; 
mVerts[index * 2 + 0] = fx + fx1; 
mVerts[index * 2 + 1] = fy + fy1; 






Mesh类的实现 
复制代码代码如下:

/* 
* System: CoreLib 
* @version 1.00 

* Copyright (C) 2010, LZT Corporation. 

*/ 
package com.nj1s.lib.mesh; 
public abstract class Mesh 

protected int WIDTH = 40; 
protected int HEIGHT = 40; 
protected int mBmpWidth = -1; 
protected int mBmpHeight = -1; 
protected final float[] mVerts; 
public Mesh(int width, int height) 

WIDTH = width; 
HEIGHT = height; 
mVerts = new float[(WIDTH + 1) * (HEIGHT + 1) * 2]; 

public float[] getVertices() 

return mVerts; 

public int getWidth() 

return WIDTH; 

public int getHeight() 

return HEIGHT; 

public static void setXY(float[] array, int index, float x, float y) 

array[index*2 + 0] = x; 
array[index*2 + 1] = y; 

public void setBitmapSize(int w, int h) 

mBmpWidth = w; 
mBmpHeight = h; 

public abstract void buildPaths(float endX, float endY); 
public abstract void buildMeshes(int index); 
public void buildMeshes(float w, float h) 

int index = 0; 
for (int y = 0; y <= HEIGHT; ++y) 

float fy = y * h / HEIGHT; 
for (int x = 0; x <= WIDTH; ++x) 

float fx = x * w / WIDTH; 
setXY(mVerts, index, fx, fy); 
index += 1; 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值