Android 吸入动画效果详解

oaka0gdM 8年前

来自: http://www.jcodecraeer.com//a/anzhuokaifa/androidkaifa/2014/0818/1655.html


1,背景


吸入(Inhale)效果,最初我是在iOS上面看到的,它是在Note程序中,用户可能添加了一页记录,在做删除时,它的删除效果是:这一页内容吸入到一个垃圾框的图标里面。请看下图所示:


===============================================================================

这里,我要介绍的是如何在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;              }          }      }  }


转自csdn:http://blog.csdn.net/leehong2005/article/details/9127095