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


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



  • 相关阅读:
    浅谈C#托管程序中的资源释放问题
    c#基本语法学习笔记
    深入剖析C#多态性
    Reporting Services API
    进程和线程的区别
    化妆品网站,饰品网站
    宠物销售网站
    卖地方特色才产品,类似湖南味道那样的网上专卖店
    建立友情链接联盟
    DIY最残忍U盘
  • 原文地址:https://www.cnblogs.com/snake-hand/p/3144909.html
Copyright © 2020-2023  润新知