Android 吸入动画效果实现分解
发布时间:2021-10-24 16:55:28
Android 吸入动画效果详解 .
这里,我要介绍的是如何在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;
}
}
}
}


猜你喜欢
- 一.BASIC认证概述在HTTP协议进行通信的过程中,HTTP协议定义了基本认证过程以允许HTTP服务器对WEB浏览器进行用户身份证的方法,
- 一、稀疏数组1、什么是稀疏数组当一个数组中大部分元素为0,或者为同一个值的数组时,可以用稀疏数组来保存该数组。稀疏数组,记录一共有几行几列,
- 使用HTTPclient访问url获得数据最近项目上有个小功能需要调用第三方的http接口取数据,用到了HTTPclient,算是做个笔记吧
- 序列化和反序列化Java是面向对象的语言,与其他语言进行交互(比如与前端js进行http通信),需要把对象转化成一种通用的格式比如json(
- 有时候会不可避免使用动态表或者列进行业务处理。下面学习几种动态表/列的使用方式:【1】使用预编译即,默认值。<select id=&q
- 用TabHost 来实现顶部选项卡,上代码:activity_main.xml<?xml version="1.0"
- 1 . pom.xml添加相关依赖<parent> <groupId>org.spring
- 本文实例为大家分享了Android实现闪屏页效果的具体代码,供大家参考,具体内容如下1.效果图2.闪屏页逻辑及布局2.1 activity_
- 1. 人机对战要增添一个人机对战的模块, 最大的难点就是如何让人机知道下在什么位置是最好的, 不仅要具备进攻的能力, 还需要具备防守的能力.
- 本文以一个asp.net程序为例讲述了Repeater中添加按钮实现点击按钮获取某一行数据的方法,分享给大家供大家参考借鉴之用。具体步骤如下
- 无论您是为具有单个处理器的计算机还是为具有多个处理器的计算机进行开发,您都希望应用程序为用户提供最好的响应性能,即使应用程序当前正在完成其他
- 随着时间的推移现在的软件要求显示的内容越来越多,所以要在小的屏幕上能够更好的显示更多的内容,首先我们会想到底部菜单栏,但是有时候想网易新闻要
- 上一篇:C# 异步多线程入门到精通之Thread篇下一篇:异步多线程之入Task,待更新启动线程池线程ThreadPool 提供的 API
- 一个基于Java Socket协议之上文件传输的完整示例,基于TCP通信完成。除了基于TCP的二进制文件传输,还演示了JAVA Swing的
- 本文实例讲述了Android MediaPlayer基本使用方法。分享给大家供大家参考,具体如下:使用MediaPlayer播放音频或者视频
- Java的Arrays类中有一个sort()方法,该方法是Arrays类的静态方法,在需要对数组进行排序时,非常的好用。但是sort()的参
- 现在很多android的应用都采用底部导航栏的功能,这样可以使得用户在使用过程中随意切换不同的页面,现在我采用TabHost组件来自定义一个
- 亲爱的读者,在这篇文章中,我提供了一些c#编程的最佳实践。你是否在用户输入验证中使用异常处理机制?如果是,那么你就是那个把你的项目执行速度降
- 1.Java Io流的概念,分类,类图。1.1 Java Io流的概念java的io是实现输入和输出的基础,可以方便的实现数据的输入和输出操
- 这篇文章主要介绍了Spring ApplicationListener * 用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具