Android 吸入动画效果详解

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的代码如下:

 

[java][/java] 

  1. mFirstPathMeasure.setPath(mFirstPath, false);
  2. mSecondPathMeasure.setPath(mSecondPath, false);
  3. float w = mBmpWidth;
  4. float h = mBmpHeight;
  5. mFirstPath.reset();
  6. mSecondPath.reset();
  7. mFirstPath.moveTo(0, 0);
  8. mSecondPath.moveTo(w, 0);
  9. mFirstPath.lineTo(0, h);
  10. mSecondPath.lineTo(w, h);
  11. mFirstPath.quadTo(0, (endY + h) / 2, endX, endY);
  12. 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,从而算出每一个顶点的坐标。(请参考上图)

下面是计算顶点坐标的详细代码:

 

 

[java][/java] 

  1. private void buildMeshByPathOnVertical(int timeIndex)
  2. {
  3.     mFirstPathMeasure.setPath(mFirstPath, false);
  4.     mSecondPathMeasure.setPath(mSecondPath, false);
  5.     int index = 0;
  6.     float[] pos1 = {0.0f, 0.0f};
  7.     float[] pos2 = {0.0f, 0.0f};
  8.     float firstLen  = mFirstPathMeasure.getLength();
  9.     float secondLen = mSecondPathMeasure.getLength();
  10.     float len1 = firstLen / HEIGHT;
  11.     float len2 = secondLen / HEIGHT;
  12.     float firstPointDist  = timeIndex * len1;
  13.     float secondPointDist = timeIndex * len2;
  14.     float height = mBmpHeight;
  15.     mFirstPathMeasure.getPosTan(firstPointDist, pos1, null);
  16.     mFirstPathMeasure.getPosTan(firstPointDist + height, pos2, null);
  17.     float x1 = pos1[0];
  18.     float x2 = pos2[0];
  19.     float y1 = pos1[1];
  20.     float y2 = pos2[1];
  21.     float FIRST_DIST  = (float)Math.sqrt( (x1 – x2) * (x1 – x2) + (y1 – y2) * (y1 – y2) );
  22.     float FIRST_H = FIRST_DIST / HEIGHT;
  23.     mSecondPathMeasure.getPosTan(secondPointDist, pos1, null);
  24.     mSecondPathMeasure.getPosTan(secondPointDist + height, pos2, null);
  25.     x1 = pos1[0];
  26.     x2 = pos2[0];
  27.     y1 = pos1[1];
  28.     y2 = pos2[1];
  29.     float SECOND_DIST = (float)Math.sqrt( (x1 – x2) * (x1 – x2) + (y1 – y2) * (y1 – y2) );
  30.     float SECOND_H = SECOND_DIST / HEIGHT;
  31.     for (int y = 0; y <= HEIGHT; ++y)
  32.     {
  33.         mFirstPathMeasure.getPosTan(y * FIRST_H + firstPointDist, pos1, null);
  34.         mSecondPathMeasure.getPosTan(y * SECOND_H + secondPointDist, pos2, null);
  35.         float w = pos2[0] – pos1[0];
  36.         float fx1 = pos1[0];
  37.         float fx2 = pos2[0];
  38.         float fy1 = pos1[1];
  39.         float fy2 = pos2[1];
  40.         float dy = fy2 – fy1;
  41.         float dx = fx2 – fx1;
  42.         for (int x = 0; x <= WIDTH; ++x)
  43.         {
  44.             // y = x * dy / dx
  45.             float fx = x * w / WIDTH;
  46.             float fy = fx * dy / dx;
  47.             mVerts[index * 2 + 0] = fx + fx1;
  48.             mVerts[index * 2 + 1] = fy + fy1;
  49.             index += 1;
  50.         }
  51.     }
  52. }

 

4,如何绘制

 

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

 

 

[java][/java]

  1. canvas.drawBitmapMesh(mBitmap,
  2.         mInhaleMesh.getWidth(),
  3.         mInhaleMesh.getHeight(),
  4.         mInhaleMesh.getVertices(),
  5.         0, null, 0, mPaint);

 

5,如何实现动画

 

[java][/java] 

  1. protected void applyTransformation(float interpolatedTime, Transformation t)
  2.         {
  3.             int curIndex = 0;
  4.             Interpolator interpolator = this.getInterpolator();
  5.             if (null != interpolator)
  6.             {
  7.                 float value = interpolator.getInterpolation(interpolatedTime);
  8.                 interpolatedTime = value;
  9.             }
  10.             if (mReverse)
  11.             {
  12.                 interpolatedTime = 1.0f – interpolatedTime;
  13.             }
  14.             curIndex = (int)(mFromIndex + (mEndIndex – mFromIndex) * interpolatedTime);
  15.             if (null != mListener)
  16.             {
  17.                 mListener.onAnimUpdate(curIndex);
  18.             }
  19.         }

在动画里面,我们计算出要做动画的帧的index,假设我们把吸入动画分为20帧,在动画里面,计算出每一帧,最后通过onAnimUpdate(int index)方法回调,在这个方法实现里面,我们根据帧的index来重新计算一个新的mesh顶点数组,再用这个数组来绘制bitmap。这样,我们就可以看来一组连续变化的mesh,也就能看到吸扩效果的动画。

 

动画类里面,最核心就是扩展Animation类,重写applyTransformation方法。

 

6,总结

 

 

本文简单介绍了吸放效果的实现,根据这个原理,我们可以构造更加复杂的Path来做更多的效果。同时,也能实现向上,向左,向右的吸入效果。

最本质是我们要理解Mesh的概念,最核心的工作就是构造出Mesh的顶点坐标。

 

计算Mesh通常是一个很复杂的工作,作一些简单的变形还可以,对于太复杂的变形,可能还是不太方便。另外,像书籍翻页的效果,用mesh其实也是可以做到的。只是算法复杂一点。

这里不能给出完整的代码,原理可能不是说得太清楚,但愿给想实现的人一个思路指引吧。

 

 

7,实现代码

 

InhaleAnimationActivity.java

 

[java][/java] 

  1. package com.nj1s.lib.test.anim;
  2. import android.os.Bundle;
  3. import android.view.Gravity;
  4. import android.view.Menu;
  5. import android.view.MenuItem;
  6. import android.view.View;
  7. import android.widget.Button;
  8. import android.widget.LinearLayout;
  9. import com.nj1s.lib.mesh.InhaleMesh.InhaleDir;
  10. import com.nj1s.lib.test.GABaseActivity;
  11. import com.nj1s.lib.test.R;
  12. import com.nj1s.lib.test.effect.BitmapMesh;
  13. public class InhaleAnimationActivity extends GABaseActivity
  14. {
  15.     private static final boolean DEBUG_MODE = false;
  16.     private BitmapMesh.SampleView mSampleView = null;
  17.     @Override
  18.     protected void onCreate(Bundle savedInstanceState)
  19.     {
  20.         super.onCreate(savedInstanceState);
  21.         LinearLayout linearLayout = new LinearLayout(this);
  22.         mSampleView = new BitmapMesh.SampleView(this);
  23.         mSampleView.setIsDebug(DEBUG_MODE);
  24.         mSampleView.setLayoutParams(new LinearLayout.LayoutParams(-1, -1));
  25.         Button btn = new Button(this);
  26.         btn.setText(“Run”);
  27.         btn.setTextSize(20.0f);
  28.         btn.setLayoutParams(new LinearLayout.LayoutParams(150, -2));
  29.         btn.setOnClickListener(new View.OnClickListener()
  30.         {
  31.             boolean mReverse = false;
  32.             @Override
  33.             public void onClick(View v)
  34.             {
  35.                 if (mSampleView.startAnimation(mReverse))
  36.                 {
  37.                     mReverse = !mReverse;
  38.                 }
  39.             }
  40.         });
  41.         linearLayout.setOrientation(LinearLayout.VERTICAL);
  42.         linearLayout.setGravity(Gravity.CENTER_VERTICAL);
  43.         linearLayout.addView(btn);
  44.         linearLayout.addView(mSampleView);
  45.         setContentView(linearLayout);
  46.     }
  47.     @Override
  48.     public boolean onCreateOptionsMenu(Menu menu)
  49.     {
  50.         getMenuInflater().inflate(R.menu.inhale_anim_menu, menu);
  51.         return true;
  52.     }
  53.     @Override
  54.     public boolean onOptionsItemSelected(MenuItem item)
  55.     {
  56.         switch(item.getItemId())
  57.         {
  58.         case R.id.menu_inhale_down:
  59.             mSampleView.setInhaleDir(InhaleDir.DOWN);
  60.             break;
  61.         case R.id.menu_inhale_up:
  62.             mSampleView.setInhaleDir(InhaleDir.UP);
  63.             break;
  64.         case R.id.menu_inhale_left:
  65.             mSampleView.setInhaleDir(InhaleDir.LEFT);
  66.             break;
  67.         case R.id.menu_inhale_right:
  68.             mSampleView.setInhaleDir(InhaleDir.RIGHT);
  69.             break;
  70.         }
  71.         return super.onOptionsItemSelected(item);
  72.     }
  73. }

 

BitmapMesh.java

 

[java][/java] 

  1. /*
  2.  * Copyright (C) 2008 The Android Open Source Project
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the “License”);
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *      http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an “AS IS” BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  */
  16. package com.nj1s.lib.test.effect;
  17. import android.content.Context;
  18. import android.graphics.Bitmap;
  19. import android.graphics.BitmapFactory;
  20. import android.graphics.Canvas;
  21. import android.graphics.Color;
  22. import android.graphics.Matrix;
  23. import android.graphics.Paint;
  24. import android.graphics.Paint.Style;
  25. import android.graphics.Path;
  26. import android.util.Log;
  27. import android.view.MotionEvent;
  28. import android.view.View;
  29. import android.view.animation.Animation;
  30. import android.view.animation.Interpolator;
  31. import android.view.animation.Transformation;
  32. import com.nj1s.lib.mesh.InhaleMesh;
  33. import com.nj1s.lib.mesh.InhaleMesh.InhaleDir;
  34. import com.nj1s.lib.test.R;
  35. public class BitmapMesh {
  36.     public static class SampleView extends View {
  37.         private static final int WIDTH = 40;
  38.         private static final int HEIGHT = 40;
  39.         private final Bitmap mBitmap;
  40.         private final Matrix mMatrix = new Matrix();
  41.         private final Matrix mInverse = new Matrix();
  42.         private boolean mIsDebug = false;
  43.         private Paint mPaint = new Paint();
  44.         private float[] mInhalePt = new float[] {0, 0};
  45.         private InhaleMesh mInhaleMesh = null;
  46.         public SampleView(Context context) {
  47.             super(context);
  48.             setFocusable(true);
  49.             mBitmap = BitmapFactory.decodeResource(getResources(),
  50.                                                      R.drawable.beach);
  51.             mInhaleMesh = new InhaleMesh(WIDTH, HEIGHT);
  52.             mInhaleMesh.setBitmapSize(mBitmap.getWidth(), mBitmap.getHeight());
  53.             mInhaleMesh.setInhaleDir(InhaleDir.DOWN);
  54.         }
  55.         public void setIsDebug(boolean isDebug)
  56.         {
  57.             mIsDebug = isDebug;
  58.         }
  59.         public void setInhaleDir(InhaleMesh.InhaleDir dir)
  60.         {
  61.             mInhaleMesh.setInhaleDir(dir);
  62.             float w = mBitmap.getWidth();
  63.             float h = mBitmap.getHeight();
  64.             float endX = 0;
  65.             float endY = 0;
  66.             float dx = 10;
  67.             float dy = 10;
  68.             mMatrix.reset();
  69.             switch (dir)
  70.             {
  71.             case DOWN:
  72.                 endX = w / 2;
  73.                 endY = getHeight() – 20;
  74.                 break;
  75.             case UP:
  76.                 dy = getHeight() – h – 20;
  77.                 endX = w / 2;
  78.                 endY = -dy + 10;
  79.                 break;
  80.             case LEFT:
  81.                 dx = getWidth() – w – 20;
  82.                 endX = -dx + 10;
  83.                 endY = h / 2;
  84.                 break;
  85.             case RIGHT:
  86.                 endX = getWidth() – 20;
  87.                 endY = h / 2;
  88.                 break;
  89.             }
  90.             mMatrix.setTranslate(dx, dy);
  91.             mMatrix.invert(mInverse);
  92.             buildPaths(endX, endY);
  93.             buildMesh(w, h);
  94.             invalidate();
  95.         }
  96.         @Override
  97.         protected void onSizeChanged(int w, int h, int oldw, int oldh)
  98.         {
  99.             super.onSizeChanged(w, h, oldw, oldh);
  100.             float bmpW = mBitmap.getWidth();
  101.             float bmpH = mBitmap.getHeight();
  102.             mMatrix.setTranslate(10, 10);
  103.             //mMatrix.setTranslate(10, 10);
  104.             mMatrix.invert(mInverse);
  105.             mPaint.setColor(Color.RED);
  106.             mPaint.setStrokeWidth(2);
  107.             mPaint.setAntiAlias(true);
  108.             buildPaths(bmpW / 2, h – 20);
  109.             buildMesh(bmpW, bmpH);
  110.         }
  111.         public boolean startAnimation(boolean reverse)
  112.         {
  113.             Animation anim = this.getAnimation();
  114.             if (null != anim && !anim.hasEnded())
  115.             {
  116.                 return false;
  117.             }
  118.             PathAnimation animation = new PathAnimation(0, HEIGHT + 1, reverse,
  119.                     new PathAnimation.IAnimationUpdateListener()
  120.             {
  121.                 @Override
  122.                 public void onAnimUpdate(int index)
  123.                 {
  124.                     mInhaleMesh.buildMeshes(index);
  125.                     invalidate();
  126.                 }
  127.             });
  128.             if (null != animation)
  129.             {
  130.                 animation.setDuration(1000);
  131.                 this.startAnimation(animation);
  132.             }
  133.             return true;
  134.         }
  135.         @Override
  136.         protected void onDraw(Canvas canvas)
  137.         {
  138.             Log.i(“leehong2”, “onDraw  =========== “);
  139.             canvas.drawColor(0xFFCCCCCC);
  140.             canvas.concat(mMatrix);
  141.             canvas.drawBitmapMesh(mBitmap,
  142.                     mInhaleMesh.getWidth(),
  143.                     mInhaleMesh.getHeight(),
  144.                     mInhaleMesh.getVertices(),
  145.                     0, null, 0, mPaint);
  146.             // ===========================================
  147.             // Draw the target point.
  148.             mPaint.setColor(Color.RED);
  149.             mPaint.setStyle(Style.FILL);
  150.             canvas.drawCircle(mInhalePt[0], mInhalePt[1], 5, mPaint);
  151.             if (mIsDebug)
  152.             {
  153.                 // ===========================================
  154.                 // Draw the mesh vertices.
  155.                 canvas.drawPoints(mInhaleMesh.getVertices(), mPaint);
  156.                 // ===========================================
  157.                 // Draw the paths
  158.                 mPaint.setColor(Color.BLUE);
  159.                 mPaint.setStyle(Style.STROKE);
  160.                 Path[] paths = mInhaleMesh.getPaths();
  161.                 for (Path path : paths)
  162.                 {
  163.                     canvas.drawPath(path, mPaint);
  164.                 }
  165.             }
  166.         }
  167.         private void buildMesh(float w, float h)
  168.         {
  169.             mInhaleMesh.buildMeshes(w, h);
  170.         }
  171.         private void buildPaths(float endX, float endY)
  172.         {
  173.             mInhalePt[0] = endX;
  174.             mInhalePt[1] = endY;
  175.             mInhaleMesh.buildPaths(endX, endY);
  176.         }
  177.         int mLastWarpX = 0;
  178.         int mLastWarpY = 0;
  179.         @Override
  180.         public boolean onTouchEvent(MotionEvent event)
  181.         {
  182.             float[] pt = { event.getX(), event.getY() };
  183.             mInverse.mapPoints(pt);
  184.             if (event.getAction() == MotionEvent.ACTION_UP)
  185.             {
  186.                 int x = (int)pt[0];
  187.                 int y = (int)pt[1];
  188.                 if (mLastWarpX != x || mLastWarpY != y) {
  189.                     mLastWarpX = x;
  190.                     mLastWarpY = y;
  191.                     buildPaths(pt[0], pt[1]);
  192.                     invalidate();
  193.                 }
  194.             }
  195.             return true;
  196.         }
  197.     }
  198.     private static class PathAnimation extends Animation
  199.     {
  200.         public interface IAnimationUpdateListener
  201.         {
  202.             public void onAnimUpdate(int index);
  203.         }
  204.         private int mFromIndex = 0;
  205.         private int mEndIndex = 0;
  206.         private boolean mReverse = false;
  207.         private IAnimationUpdateListener mListener = null;
  208.         public PathAnimation(int fromIndex, int endIndex, boolean reverse, IAnimationUpdateListener listener)
  209.         {
  210.             mFromIndex = fromIndex;
  211.             mEndIndex = endIndex;
  212.             mReverse = reverse;
  213.             mListener = listener;
  214.         }
  215.         public boolean getTransformation(long currentTime, Transformation outTransformation) {
  216.             boolean more = super.getTransformation(currentTime, outTransformation);
  217.             Log.d(“leehong2”, “getTransformation    more = ” + more);
  218.             return more;
  219.         }
  220.         @Override
  221.         protected void applyTransformation(float interpolatedTime, Transformation t)
  222.         {
  223.             int curIndex = 0;
  224.             Interpolator interpolator = this.getInterpolator();
  225.             if (null != interpolator)
  226.             {
  227.                 float value = interpolator.getInterpolation(interpolatedTime);
  228.                 interpolatedTime = value;
  229.             }
  230.             if (mReverse)
  231.             {
  232.                 interpolatedTime = 1.0f – interpolatedTime;
  233.             }
  234.             curIndex = (int)(mFromIndex + (mEndIndex – mFromIndex) * interpolatedTime);
  235.             if (null != mListener)
  236.             {
  237.                 Log.i(“leehong2”, “onAnimUpdate  =========== curIndex = ” + curIndex);
  238.                 mListener.onAnimUpdate(curIndex);
  239.             }
  240.         }
  241.     }
  242. }

 

最核心的类

InhaleMesh

[java][/java] 

  1. package com.nj1s.lib.mesh;
  2. import android.graphics.Path;
  3. import android.graphics.PathMeasure;
  4. public class InhaleMesh extends Mesh
  5. {
  6.     public enum InhaleDir
  7.     {
  8.         UP,
  9.         DOWN,
  10.         LEFT,
  11.         RIGHT,
  12.     }
  13.     private Path mFirstPath  = new Path();
  14.     private Path mSecondPath = new Path();
  15.     private PathMeasure mFirstPathMeasure  = new PathMeasure();
  16.     private PathMeasure mSecondPathMeasure = new PathMeasure();
  17.     private InhaleDir mInhaleDir = InhaleDir.DOWN;
  18.     public InhaleMesh(int width, int height)
  19.     {
  20.         super(width, height);
  21.     }
  22.     public void setInhaleDir(InhaleDir inhaleDir)
  23.     {
  24.         mInhaleDir = inhaleDir;
  25.     }
  26.     public InhaleDir getInhaleDir()
  27.     {
  28.         return mInhaleDir;
  29.     }
  30.     @Override
  31.     public void buildPaths(float endX, float endY)
  32.     {
  33.         if (mBmpWidth <= 0 || mBmpHeight <= 0)
  34.         {
  35.             throw new IllegalArgumentException(
  36.                     “Bitmap size must be > 0, do you call setBitmapSize(int, int) method?”);
  37.         }
  38.         switch (mInhaleDir)
  39.         {
  40.         case UP:
  41.             buildPathsUp(endX, endY);
  42.             break;
  43.         case DOWN:
  44.             buildPathsDown(endX, endY);
  45.             break;
  46.         case RIGHT:
  47.             buildPathsRight(endX, endY);
  48.             break;
  49.         case LEFT:
  50.             buildPathsLeft(endX, endY);
  51.             break;
  52.         }
  53.     }
  54.     @Override
  55.     public void buildMeshes(int index)
  56.     {
  57.         if (mBmpWidth <= 0 || mBmpHeight <= 0)
  58.         {
  59.             throw new IllegalArgumentException(
  60.                     “Bitmap size must be > 0, do you call setBitmapSize(int, int) method?”);
  61.         }
  62.         switch (mInhaleDir)
  63.         {
  64.         case UP:
  65.         case DOWN:
  66.             buildMeshByPathOnVertical(index);
  67.             break;
  68.         case RIGHT:
  69.         case LEFT:
  70.             buildMeshByPathOnHorizontal(index);
  71.             break;
  72.         }
  73.     }
  74.     public Path[] getPaths()
  75.     {
  76.         return new Path[] { mFirstPath, mSecondPath };
  77.     }
  78.     private void buildPathsDown(float endX, float endY)
  79.     {
  80.         mFirstPathMeasure.setPath(mFirstPath, false);
  81.         mSecondPathMeasure.setPath(mSecondPath, false);
  82.         float w = mBmpWidth;
  83.         float h = mBmpHeight;
  84.         mFirstPath.reset();
  85.         mSecondPath.reset();
  86.         mFirstPath.moveTo(0, 0);
  87.         mSecondPath.moveTo(w, 0);
  88.         mFirstPath.lineTo(0, h);
  89.         mSecondPath.lineTo(w, h);
  90.         mFirstPath.quadTo(0, (endY + h) / 2, endX, endY);
  91.         mSecondPath.quadTo(w, (endY + h) / 2, endX, endY);
  92.     }
  93.     private void buildPathsUp(float endX, float endY)
  94.     {
  95.         mFirstPathMeasure.setPath(mFirstPath, false);
  96.         mSecondPathMeasure.setPath(mSecondPath, false);
  97.         float w = mBmpWidth;
  98.         float h = mBmpHeight;
  99.         mFirstPath.reset();
  100.         mSecondPath.reset();
  101.         mFirstPath.moveTo(0, h);
  102.         mSecondPath.moveTo(w, h);
  103.         mFirstPath.lineTo(0, 0);
  104.         mSecondPath.lineTo(w, 0);
  105.         mFirstPath.quadTo(0, (endY – h) / 2, endX, endY);
  106.         mSecondPath.quadTo(w, (endY – h) / 2, endX, endY);
  107.     }
  108.     private void buildPathsRight(float endX, float endY)
  109.     {
  110.         mFirstPathMeasure.setPath(mFirstPath, false);
  111.         mSecondPathMeasure.setPath(mSecondPath, false);
  112.         float w = mBmpWidth;
  113.         float h = mBmpHeight;
  114.         mFirstPath.reset();
  115.         mSecondPath.reset();
  116.         mFirstPath.moveTo(0, 0);
  117.         mSecondPath.moveTo(0, h);
  118.         mFirstPath.lineTo(w, 0);
  119.         mSecondPath.lineTo(w, h);
  120.         mFirstPath.quadTo((endX + w) / 2, 0, endX, endY);
  121.         mSecondPath.quadTo((endX + w) / 2, h, endX, endY);
  122.     }
  123.     private void buildPathsLeft(float endX, float endY)
  124.     {
  125.         mFirstPathMeasure.setPath(mFirstPath, false);
  126.         mSecondPathMeasure.setPath(mSecondPath, false);
  127.         float w = mBmpWidth;
  128.         float h = mBmpHeight;
  129.         mFirstPath.reset();
  130.         mSecondPath.reset();
  131.         mFirstPath.moveTo(w, 0);
  132.         mSecondPath.moveTo(w, h);
  133.         mFirstPath.lineTo(0, 0);
  134.         mSecondPath.lineTo(0, h);
  135.         mFirstPath.quadTo((endX – w) / 2, 0, endX, endY);
  136.         mSecondPath.quadTo((endX – w) / 2, h, endX, endY);
  137.     }
  138.     private void buildMeshByPathOnVertical(int timeIndex)
  139.     {
  140.         mFirstPathMeasure.setPath(mFirstPath, false);
  141.         mSecondPathMeasure.setPath(mSecondPath, false);
  142.         int index = 0;
  143.         float[] pos1 = {0.0f, 0.0f};
  144.         float[] pos2 = {0.0f, 0.0f};
  145.         float firstLen  = mFirstPathMeasure.getLength();
  146.         float secondLen = mSecondPathMeasure.getLength();
  147.         float len1 = firstLen / HEIGHT;
  148.         float len2 = secondLen / HEIGHT;
  149.         float firstPointDist  = timeIndex * len1;
  150.         float secondPointDist = timeIndex * len2;
  151.         float height = mBmpHeight;
  152.         mFirstPathMeasure.getPosTan(firstPointDist, pos1, null);
  153.         mFirstPathMeasure.getPosTan(firstPointDist + height, pos2, null);
  154.         float x1 = pos1[0];
  155.         float x2 = pos2[0];
  156.         float y1 = pos1[1];
  157.         float y2 = pos2[1];
  158.         float FIRST_DIST  = (float)Math.sqrt( (x1 – x2) * (x1 – x2) + (y1 – y2) * (y1 – y2) );
  159.         float FIRST_H = FIRST_DIST / HEIGHT;
  160.         mSecondPathMeasure.getPosTan(secondPointDist, pos1, null);
  161.         mSecondPathMeasure.getPosTan(secondPointDist + height, pos2, null);
  162.         x1 = pos1[0];
  163.         x2 = pos2[0];
  164.         y1 = pos1[1];
  165.         y2 = pos2[1];
  166.         float SECOND_DIST = (float)Math.sqrt( (x1 – x2) * (x1 – x2) + (y1 – y2) * (y1 – y2) );
  167.         float SECOND_H = SECOND_DIST / HEIGHT;
  168.         if (mInhaleDir == InhaleDir.DOWN)
  169.         {
  170.             for (int y = 0; y <= HEIGHT; ++y)
  171.             {
  172.                 mFirstPathMeasure.getPosTan(y * FIRST_H + firstPointDist, pos1, null);
  173.                 mSecondPathMeasure.getPosTan(y * SECOND_H + secondPointDist, pos2, null);
  174.                 float w = pos2[0] – pos1[0];
  175.                 float fx1 = pos1[0];
  176.                 float fx2 = pos2[0];
  177.                 float fy1 = pos1[1];
  178.                 float fy2 = pos2[1];
  179.                 float dy = fy2 – fy1;
  180.                 float dx = fx2 – fx1;
  181.                 for (int x = 0; x <= WIDTH; ++x)
  182.                 {
  183.                     // y = x * dy / dx
  184.                     float fx = x * w / WIDTH;
  185.                     float fy = fx * dy / dx;
  186.                     mVerts[index * 2 + 0] = fx + fx1;
  187.                     mVerts[index * 2 + 1] = fy + fy1;
  188.                     index += 1;
  189.                 }
  190.             }
  191.         }
  192.         else if (mInhaleDir == InhaleDir.UP)
  193.         {
  194.             for (int y = HEIGHT; y >= 0; –y)
  195.             {
  196.                 mFirstPathMeasure.getPosTan(y * FIRST_H + firstPointDist, pos1, null);
  197.                 mSecondPathMeasure.getPosTan(y * SECOND_H + secondPointDist, pos2, null);
  198.                 float w = pos2[0] – pos1[0];
  199.                 float fx1 = pos1[0];
  200.                 float fx2 = pos2[0];
  201.                 float fy1 = pos1[1];
  202.                 float fy2 = pos2[1];
  203.                 float dy = fy2 – fy1;
  204.                 float dx = fx2 – fx1;
  205.                 for (int x = 0; x <= WIDTH; ++x)
  206.                 {
  207.                     // y = x * dy / dx
  208.                     float fx = x * w / WIDTH;
  209.                     float fy = fx * dy / dx;
  210.                     mVerts[index * 2 + 0] = fx + fx1;
  211.                     mVerts[index * 2 + 1] = fy + fy1;
  212.                     index += 1;
  213.                 }
  214.             }
  215.         }
  216.     }
  217.     private void buildMeshByPathOnHorizontal(int timeIndex)
  218.     {
  219.         mFirstPathMeasure.setPath(mFirstPath, false);
  220.         mSecondPathMeasure.setPath(mSecondPath, false);
  221.         int index = 0;
  222.         float[] pos1 = {0.0f, 0.0f};
  223.         float[] pos2 = {0.0f, 0.0f};
  224.         float firstLen  = mFirstPathMeasure.getLength();
  225.         float secondLen = mSecondPathMeasure.getLength();
  226.         float len1 = firstLen / WIDTH;
  227.         float len2 = secondLen / WIDTH;
  228.         float firstPointDist  = timeIndex * len1;
  229.         float secondPointDist = timeIndex * len2;
  230.         float width = mBmpWidth;
  231.         mFirstPathMeasure.getPosTan(firstPointDist, pos1, null);
  232.         mFirstPathMeasure.getPosTan(firstPointDist + width, pos2, null);
  233.         float x1 = pos1[0];
  234.         float x2 = pos2[0];
  235.         float y1 = pos1[1];
  236.         float y2 = pos2[1];
  237.         float FIRST_DIST  = (float)Math.sqrt( (x1 – x2) * (x1 – x2) + (y1 – y2) * (y1 – y2) );
  238.         float FIRST_X = FIRST_DIST / WIDTH;
  239.         mSecondPathMeasure.getPosTan(secondPointDist, pos1, null);
  240.         mSecondPathMeasure.getPosTan(secondPointDist + width, pos2, null);
  241.         x1 = pos1[0];
  242.         x2 = pos2[0];
  243.         y1 = pos1[1];
  244.         y2 = pos2[1];
  245.         float SECOND_DIST = (float)Math.sqrt( (x1 – x2) * (x1 – x2) + (y1 – y2) * (y1 – y2) );
  246.         float SECOND_X = SECOND_DIST / WIDTH;
  247.         if (mInhaleDir == InhaleDir.RIGHT)
  248.         {
  249.             for (int x = 0; x <= WIDTH; ++x)
  250.             {
  251.                 mFirstPathMeasure.getPosTan(x * FIRST_X + firstPointDist, pos1, null);
  252.                 mSecondPathMeasure.getPosTan(x * SECOND_X + secondPointDist, pos2, null);
  253.                 float h = pos2[1] – pos1[1];
  254.                 float fx1 = pos1[0];
  255.                 float fx2 = pos2[0];
  256.                 float fy1 = pos1[1];
  257.                 float fy2 = pos2[1];
  258.                 float dy = fy2 – fy1;
  259.                 float dx = fx2 – fx1;
  260.                 for (int y = 0; y <= HEIGHT; ++y)
  261.                 {
  262.                     // x = y * dx / dy
  263.                     float fy = y * h / HEIGHT;
  264.                     float fx = fy * dx / dy;
  265.                     index = y * (WIDTH + 1) + x;
  266.                     mVerts[index * 2 + 0] = fx + fx1;
  267.                     mVerts[index * 2 + 1] = fy + fy1;
  268.                 }
  269.             }
  270.         }
  271.         else if (mInhaleDir == InhaleDir.LEFT)
  272.         {
  273.             for (int x = WIDTH; x >= 0; –x)
  274.             //for (int x = 0; x <= WIDTH; ++x)
  275.             {
  276.                 mFirstPathMeasure.getPosTan(x * FIRST_X + firstPointDist, pos1, null);
  277.                 mSecondPathMeasure.getPosTan(x * SECOND_X + secondPointDist, pos2, null);
  278.                 float h = pos2[1] – pos1[1];
  279.                 float fx1 = pos1[0];
  280.                 float fx2 = pos2[0];
  281.                 float fy1 = pos1[1];
  282.                 float fy2 = pos2[1];
  283.                 float dy = fy2 – fy1;
  284.                 float dx = fx2 – fx1;
  285.                 for (int y = 0; y <= HEIGHT; ++y)
  286.                 {
  287.                     // x = y * dx / dy
  288.                     float fy = y * h / HEIGHT;
  289.                     float fx = fy * dx / dy;
  290.                     index = y * (WIDTH + 1) + WIDTH – x;
  291.                     mVerts[index * 2 + 0] = fx + fx1;
  292.                     mVerts[index * 2 + 1] = fy + fy1;
  293.                 }
  294.             }
  295.         }
  296.     }
  297. }

Mesh类的实现

[java][/java] 

  1. /*
  2.  * System: CoreLib
  3.  * @version     1.00
  4.  *
  5.  * Copyright (C) 2010, LZT Corporation.
  6.  *
  7.  */
  8. package com.nj1s.lib.mesh;
  9. public abstract class Mesh
  10. {
  11.     protected int WIDTH      = 40;
  12.     protected int HEIGHT     = 40;
  13.     protected int mBmpWidth   = -1;
  14.     protected int mBmpHeight  = -1;
  15.     protected final float[] mVerts;
  16.     public Mesh(int width, int height)
  17.     {
  18.         WIDTH  = width;
  19.         HEIGHT = height;
  20.         mVerts  = new float[(WIDTH + 1) * (HEIGHT + 1) * 2];
  21.     }
  22.     public float[] getVertices()
  23.     {
  24.         return mVerts;
  25.     }
  26.     public int getWidth()
  27.     {
  28.         return WIDTH;
  29.     }
  30.     public int getHeight()
  31.     {
  32.         return HEIGHT;
  33.     }
  34.     public static void setXY(float[] array, int index, float x, float y)
  35.     {
  36.         array[index*2 + 0] = x;
  37.         array[index*2 + 1] = y;
  38.     }
  39.     public void setBitmapSize(int w, int h)
  40.     {
  41.         mBmpWidth  = w;
  42.         mBmpHeight = h;
  43.     }
  44.     public abstract void buildPaths(float endX, float endY);
  45.     public abstract void buildMeshes(int index);
  46.     public void buildMeshes(float w, float h)
  47.     {
  48.         int index = 0;
  49.         for (int y = 0; y <= HEIGHT; ++y)
  50.         {
  51.             float fy = y * h / HEIGHT;
  52.             for (int x = 0; x <= WIDTH; ++x)
  53.             {
  54.                 float fx = x * w / WIDTH;
  55.                 setXY(mVerts, index, fx, fy);
  56.                 index += 1;
  57.             }
  58.         }
  59.     }
  60. }

标签