Android 吸入動畫效果詳解
來自: http://www.jcodecraeer.com//a/anzhuokaifa/androidkaifa/2014/0818/1655.html
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; } } } }
轉自csdn:http://blog.csdn.net/leehong2005/article/details/9127095