转载自:http://m.blog.csdn.net/blog/leehong2005/9127095

[转]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 / dxfloat 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;@Overrideprotected 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;@Overridepublic 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);}@Overridepublic boolean onCreateOptionsMenu(Menu menu) {getMenuInflater().inflate(R.menu.inhale_anim_menu, menu);return true;}@Overridepublic 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();}@Overrideprotected 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(){@Overridepublic 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 pathsmPaint.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;}@Overrideprotected 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;}@Overridepublic 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;}}@Overridepublic 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 / dxfloat 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 / dxfloat 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 / dyfloat 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 / dyfloat 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;}}}
}

Android 吸入动画效果详解(仿mac退出效果)相关推荐

  1. Android Animator(动画)类 详解

    Android Animator动画类 详解 动画分为3种 ValueAnimator类 ObjectAnimator类 AnimatorSet类 Animator的addListener()方法(监 ...

  2. Android Animations动画使用详解

    一.动画类型 Android的animation由四种类型组成:alpha.scale.translate.rotate XML配置文件中 alpha 渐变透明度动画效果 scale 渐变尺寸伸缩动画 ...

  3. android 弹性动画,SpringAnimation详解-Android

    SpringAnimation 类是最近(25.3.0版本)才添加在支持库中的一个类,它主要是为了让实现弹性动画变得更加方便,其实facebook在很久以前的Rebound库就实现了这样的动画,而且效 ...

  4. Android 动画框架详解,第 1 部分

    2019独角兽企业重金招聘Python工程师标准>>> Android 平台提供了一套完整的动画框架,使得开发者可以用它来开发各种动画效果,本文将向读者阐述 Android 的动画框 ...

  5. ANDROID L——Material Design详解(动画篇)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! Android L: Google已经确认Android L就是Android Lolli ...

  6. Android 动画框架详解

    Android 动画框架详解 基本原理 朱 韦伟, 软件工程师, IBM 李 浩, 软件工程师, 爱格码 简介: Android 平台提供了一套完整的动画框架,使得开发者可以用它来开发各种动画效果.A ...

  7. Android百度地图实例详解之仿摩拜单车APP(包括附近车辆、规划路径、行驶距离、行驶轨迹记录,导航等)

    Android百度地图实例详解之仿摩拜单车APP(包括附近车辆.规划路径.行驶距离.行驶轨迹记录,导航等) 标签: android百度地图行驶轨迹记录共享单车行驶距离和时间 2017-03-08 20 ...

  8. android 动画坐标,Android应用坐标系统全面详解

    1 背景 去年有很多人私信告诉我让说说自定义控件,其实通观网络上的很多博客都在讲各种自定义控件,但是大多数都是授之以鱼,却很少有较为系统性授之于渔的文章,同时由于自己也迟迟没有时间规划这一系列文章,最 ...

  9. Android应用坐标系统全面详解

    Android应用坐标系统全面详解 原文链接:CSDN@工匠若水,http://blog.csdn.net/yanbober/article/details/50419117 1. 背景 去年有很多人 ...

最新文章

  1. 11、修改和删除索引(DROP INDEX)
  2. C++中extern关键字使用 http://blog.csdn.net/sruru/article/details/7951019
  3. linux io测试陈旭,陈旭方案论证及器件选择.doc
  4. python datetime
  5. C#通用类库--DOS常用命令
  6. 年薪 66万+,西澳大学招聘 CV DL Research Fellow(研究员)
  7. C 语言基础——程序运行时间的计算
  8. C++基础::limits
  9. 打开一个php网页出现2个ip,php根据ip地址查地区
  10. CSS font-family字体大合集
  11. 【论文笔记】Contextual Diversity for Active Learning(ECCV2020)
  12. 使用lxml爬取豆瓣电影排行榜
  13. 【Python】wo ai ni python代码画画
  14. 【Vim】No write since last change
  15. 绿米Aara单火开关,ZigBee智能开关和单火线取电技术, 对于单火取电电源和ZIGBEE缩合分析
  16. 什么叫组网_小米科普:一文看懂路由器上的 Mesh 组网是什么
  17. int、Integer、new Integer和Integer.valueOf()的 ==、equals比较
  18. 避免使用隐式类型转换
  19. 7-2 愤怒的牛 (25分)
  20. (NO.00001)iOS游戏SpeedBoy Lite成形记(十九)

热门文章

  1. 手把手教你如何用python制作自动翻译程序
  2. 史上最简单的图像识别-百度AI开放平台
  3. 鸿蒙os正式版推送时间,鸿蒙OS正式版推送时间确定,游戏性能更强,流畅度稳定性均提升...
  4. cpu的主频、外频和倍频
  5. 心情顿时很失落的感觉!
  6. 2023,本命年向阳而生
  7. 大数据分析案例-基于XGBoost算法构建二手车价格评估模型
  8. 004/160 CrackMe ajj CKme
  9. 【3分钟高效制作PPT】ChatGPT搭配闪击PPT生成PPT,你只需要输入一个标题
  10. matlab 画图基本