Android 吸入动画效果详解(仿mac退出效果)

2024-09-05 17:58

本文主要是介绍Android 吸入动画效果详解(仿mac退出效果),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转载自: 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退出效果)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1139671

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

防近视护眼台灯什么牌子好?五款防近视效果好的护眼台灯推荐

在家里,灯具是属于离不开的家具,每个大大小小的地方都需要的照亮,所以一盏好灯是必不可少的,每个发挥着作用。而护眼台灯就起了一个保护眼睛,预防近视的作用。可以保护我们在学习,阅读的时候提供一个合适的光线环境,保护我们的眼睛。防近视护眼台灯什么牌子好?那我们怎么选择一个优秀的护眼台灯也是很重要,才能起到最大的护眼效果。下面五款防近视效果好的护眼台灯推荐: 一:六个推荐防近视效果好的护眼台灯的

K8S(Kubernetes)开源的容器编排平台安装步骤详解

K8S(Kubernetes)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。以下是K8S容器编排平台的安装步骤、使用方式及特点的概述: 安装步骤: 安装Docker:K8S需要基于Docker来运行容器化应用程序。首先要在所有节点上安装Docker引擎。 安装Kubernetes Master:在集群中选择一台主机作为Master节点,安装K8S的控制平面组件,如AP

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存