Android 吸入动画效果详解

来源:互联网 发布:知乎 盐水去甲醛 编辑:程序博客网 时间:2024/04/27 20:01

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


4,如何绘制


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


[java] view plaincopy
  1. canvas.drawBitmapMesh(mBitmap,  
  2.         mInhaleMesh.getWidth(),  
  3.         mInhaleMesh.getHeight(),  
  4.         mInhaleMesh.getVertices(),  
  5.         0null0, mPaint);  


5,如何实现动画

[java] view plaincopy
  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.   
  11.             if (mReverse)  
  12.             {  
  13.                 interpolatedTime = 1.0f - interpolatedTime;  
  14.             }  
  15.   
  16.             curIndex = (int)(mFromIndex + (mEndIndex - mFromIndex) * interpolatedTime);  
  17.   
  18.             if (null != mListener)  
  19.             {  
  20.                 mListener.onAnimUpdate(curIndex);  
  21.             }  
  22.         }  

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

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


6,总结


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

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


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

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


7,实现代码


InhaleAnimationActivity.java

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


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

最核心的类

InhaleMesh


Mesh类的实现

[java] view plaincopy/*  * 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;              }          }      }  }  




最核心的类

InhaleMesh

0 0