Android动画之小球拟合动画实例

Android动画之小球拟合动画实例

实现效果:

动画组成:

1.通过三阶贝塞尔曲线来拟合圆,拟合系数的由来,以及怎么选控制点.

2.利用画布canvas.translate,以及scale,rotate的方法,来渐变绘制的过程.

3.熟悉拟合过程.

4.不熟悉的话,先绘制辅助点的移动路线,对理解两个圆的分裂的拟合过程有好处.

package com.example.administrator.animationworkdemo.views;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.util.AttributeSet;
import android.view.View;

import java.util.concurrent.CyclicBarrier;

/**
 * 这个例子中,大家可以发现作者的拟合做的并不是很好,连接的地方比较生硬,大家可以思考下如何改善
 * 贝塞尔曲线绘制比较复杂,大家在学习过程中,可以仿照示例中的,将辅助点和线绘制出来,这样会看的更清楚一点
 */
public class BallShapeChangeView extends View {

  // 使用贝塞尔曲线来拟合圆的magic number
  //C 是三阶贝塞尔曲线拟合 圆的 误差最小  获得控制点的参数.
  private static final float C = 0.551915024494f;
  private Paint mPaint;
  private int mRadiusBig = 120, mRadiusSmall = (int) (mRadiusBig / 2f), mWidth, mHeight, mMimWidth = (int) (mRadiusSmall * 2 * 3)/*fill view mim width*/;
  private float mFraction = 0, mFractionDegree = 0, /*degree*/
      mLength, mDistanceBezier;
  private Path mPathCircle, mPathBezier;
  private ValueAnimator mValueAnimator;
  private float[] mPointData = new float[8];// 4个数据点 顺时针排序,从左边开始
  private float[] mPointCtrl = new float[16];// 8个控制点
  private float[] mPos = new float[2];
  private PathMeasure mPathMeasure;
  private Path mPathBezier2;

  public BallShapeChangeView(Context context, AttributeSet attrs) {
    super(context, attrs);
    mPaint = new Paint();
    mPaint.setStyle(Paint.Style.FILL);
    mPaint.setColor(0xFF7C191E);
    mPaint.setAntiAlias(true);
    mPathCircle = new Path();
    mPathBezier = new Path();
    mPathBezier2 = new Path();
    mPathMeasure = new PathMeasure();
    mValueAnimator = ValueAnimator.ofFloat(0, 1, 0);
    mValueAnimator.setDuration(3000);
    mValueAnimator.setRepeatCount(Integer.MAX_VALUE);
    mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        mFraction = (float) animation.getAnimatedValue();
        mFractionDegree = animation.getAnimatedFraction();
        invalidate();
      }
    });
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 为了能够更好的控制绘制的大小和位置,当然,初学者写死也是可以的
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    mWidth = MeasureSpec.getSize(widthMeasureSpec);
    mHeight = MeasureSpec.getSize(heightMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    if (widthMode != MeasureSpec.AT_MOST && heightMode != MeasureSpec.AT_MOST) {
      if (mWidth < mMimWidth)
        mWidth = mMimWidth;
      if (mHeight < mMimWidth)
        mHeight = mMimWidth;
    } else if (widthMeasureSpec != MeasureSpec.AT_MOST) {
      if (mWidth < mMimWidth)
        mWidth = mMimWidth;
    } else if (heightMeasureSpec != MeasureSpec.AT_MOST) {
      if (mHeight < mMimWidth)
        mHeight = mMimWidth;
    }
    setMeasuredDimension(mWidth, mHeight);
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // 通过mFraction来控制绘图的过程,这是常用的一种方式
    canvas.translate(mWidth / 2, mHeight / 2);
    canvas.scale(1, -1);
    canvas.rotate(-360 * mFractionDegree);
    setDoubleCirClePath();
    canvas.drawPath(mPathCircle, mPaint);
    if (mFraction < (1 / 3f)) {// 缩小大圆
      setCirclePath();
      canvas.drawPath(mPathCircle, mPaint);
    } else if (mFraction < 3 / 4f) {// 画贝塞尔曲线
      setBezierPath2();
      canvas.drawPath(mPathBezier, mPaint);
      canvas.drawPath(mPathBezier2, mPaint);
    } else {// 画分离
      //setLastBezierPath();
      //canvas.drawPath(mPathBezier, mPaint);
    }
  }

  private void setDoubleCirClePath() {
    mPathCircle.reset();
    if (mFraction < (1 / 3f)) {
      mPathCircle.addCircle(-mRadiusSmall / 2f * mFraction * 3, 0, mRadiusSmall, Path.Direction.CW);
      mPathCircle.addCircle(mRadiusSmall / 2f * mFraction * 3, 0, mRadiusSmall, Path.Direction.CW);
    } else {
      float distance = (mFraction - 1 / 3f) / (2 / 3f) * (mRadiusSmall * 2 + mRadiusSmall / 2f);
      mPathCircle.addCircle(-mRadiusSmall / 2f - distance, 0, mRadiusSmall, Path.Direction.CW);
      mPathCircle.addCircle(mRadiusSmall / 2f + distance, 0, mRadiusSmall, Path.Direction.CW);
    }
  }

  // mFraction 0 ~ 1/3
  private void setCirclePath() {
    mPointData[0] = -mRadiusBig + mRadiusSmall / 2f * mFraction * 3f;
    mPointData[1] = 0;
    mPointData[2] = 0;
    mPointData[3] = mRadiusBig - mRadiusBig / 2f * mFraction * 3f;//0到1 的三分之一 用来给大圆做效果;
    mPointData[4] = mRadiusBig - mRadiusSmall / 2f * mFraction * 3f;
    mPointData[5] = 0;
    mPointData[6] = mPointData[2];
    mPointData[7] = -mPointData[3];
    mPointCtrl[0] = mPointData[0];// x轴一样
    mPointCtrl[1] = mRadiusBig * C;// y轴向下的
    mPointCtrl[2] = mPointData[2] - mRadiusBig * C;
    mPointCtrl[3] = mPointData[3];// y轴一样
    mPointCtrl[4] = mPointData[2] + mRadiusBig * C;
    mPointCtrl[5] = mPointData[3];
    mPointCtrl[6] = mPointData[4];
    mPointCtrl[7] = mPointCtrl[1];
    mPointCtrl[8] = mPointData[4];
    mPointCtrl[9] = -mPointCtrl[1];
    mPointCtrl[10] = mPointCtrl[4];
    mPointCtrl[11] = mPointData[7];
    mPointCtrl[12] = mPointCtrl[2];
    mPointCtrl[13] = mPointData[7];
    mPointCtrl[14] = mPointData[0];
    mPointCtrl[15] = -mPointCtrl[1];
    mPathCircle.reset();
    mPathCircle.moveTo(mPointData[0], mPointData[1]);
    mPathCircle.cubicTo(mPointCtrl[0], mPointCtrl[1], mPointCtrl[2], mPointCtrl[3], mPointData[2], mPointData[3]);
    mPathCircle.cubicTo(mPointCtrl[4], mPointCtrl[5], mPointCtrl[6], mPointCtrl[7], mPointData[4], mPointData[5]);
    mPathCircle.cubicTo(mPointCtrl[8], mPointCtrl[9], mPointCtrl[10], mPointCtrl[11], mPointData[6], mPointData[7]);
    mPathCircle.cubicTo(mPointCtrl[12], mPointCtrl[13], mPointCtrl[14], mPointCtrl[15], mPointData[0], mPointData[1]);
  }

  // mFraction 1/3 ~ 3/4
  private void setBezierPath2() {
    mPointData[0] = -mRadiusSmall / 2 - (mFraction - 1 / 3f) * mRadiusBig * 2f;
    if (mFraction < 2 / 3f) {
      mPointData[1] = -mRadiusSmall;
    } else {
      mPointData[1] = -mRadiusSmall + (mFraction - 2 / 3f) * 3 * mRadiusSmall;
    }
    if (mFraction < 3 / 4f) {
      mPointData[2] = 0;
    } else {
      //当分裂超过一定程度让结束点的位置变远
      mPointData[2] = (mFraction - 3 / 4f) * 16 * mPointData[0];
    }
    //当动画执行进度大于2/3时,此时该点接近于0
    mPointData[3] = -mRadiusBig + mFraction * mRadiusBig * 1.5f < -0.01f * mRadiusBig ? -mRadiusBig + mFraction * mRadiusBig * 1.5f : 0.01f * -mRadiusBig;

    mPointData[4] = mPointData[2];
    mPointData[5] = -mPointData[3];

    mPointData[6] = mPointData[0];
    mPointData[7] = -mPointData[1];

    mPointCtrl[0] = mPointData[0] + mRadiusSmall;
    mPointCtrl[1] = mPointData[3];

    mPointCtrl[2] = mPointData[0] + mRadiusSmall;
    mPointCtrl[3] = -mPointData[3];

    mPathBezier.reset();
    mPathBezier.moveTo(mPointData[0], mPointData[1]);
    mPathBezier.quadTo(mPointCtrl[0], mPointCtrl[1], mPointData[2], mPointData[3]);
    mPathBezier.lineTo(mPointData[4], mPointData[5]);
    mPathBezier.quadTo(mPointCtrl[2], mPointCtrl[3], mPointData[6], mPointData[7]);

    mPathBezier2.reset();
    mPathBezier2.moveTo(-mPointData[0], mPointData[1]);
    mPathBezier2.quadTo(-mPointCtrl[0], mPointCtrl[1], -mPointData[2], mPointData[3]);
    mPathBezier2.lineTo(-mPointData[4], mPointData[5]);
    mPathBezier2.quadTo(-mPointCtrl[2], mPointCtrl[3], -mPointData[6], mPointData[7]);

  }

  // mFraction 1/3 ~ 3/4
  private void setBezierPath() {
    mPathBezier.reset();
    float distance = (2 * mRadiusSmall + mRadiusSmall / 2f) * mFraction;
    //float topY = mRadiusSmall * (1 - 0.6f * mFraction);
    float topY = mRadiusSmall - mRadiusSmall * (mFraction - 1 / 3f);
    float distanceBezier = topY - distance * C * (0.5f + 0.5f * mFraction);
    if (mDistanceBezier != 0 && distanceBezier < (mDistanceBezier)) {
      distanceBezier = mDistanceBezier;
    }
    mPathBezier.moveTo(-distance, topY);
    mPathBezier.cubicTo(-distance, distanceBezier, distance, distanceBezier, distance, topY);
    if (mDistanceBezier == 0) {
      mPathMeasure.setPath(mPathBezier, false);
      mLength = mPathMeasure.getLength();
      mPathMeasure.getPosTan(mLength / 2, mPos, null);
      if (mPos[1] <= 8) {
        mDistanceBezier = distanceBezier;
        mPathBezier.reset();
        mPathBezier.moveTo(-distance, topY);
        mPathBezier.cubicTo(-distance, mDistanceBezier, distance, mDistanceBezier, distance, topY);
        mPathBezier.lineTo(distance, -topY);
        mPathBezier.cubicTo(distance, -mDistanceBezier, -distance, -mDistanceBezier, -distance, -topY);
        mPathBezier.close();
        return;
      }
    }
    mPathBezier.lineTo(distance, -topY);
    mPathBezier.cubicTo(distance, -distanceBezier, -distance, -distanceBezier, -distance, -topY);
    mPathBezier.close();
  }

  // mFraction 3/4 ~ 1
  private void setLastBezierPath() {
    float x = -mRadiusSmall / 2f - (mFraction - 1 / 3f) / (2 / 3f) * (mRadiusSmall * 2 + mRadiusSmall / 2f);
    mPathBezier.reset();
    mPathBezier.moveTo(x, mRadiusSmall);
    mPathBezier.quadTo(x, 0, x + mRadiusSmall + mRadiusSmall * (4 - mFraction * 4), 0);
    mPathBezier.quadTo(x, 0, x, -mRadiusSmall);
    mPathBezier.lineTo(x, mRadiusSmall);
    mPathBezier.moveTo(-x, mRadiusSmall);
    mPathBezier.quadTo(-x, 0, -x - mRadiusSmall - mRadiusSmall * (4 - mFraction * 4), 0);
    mPathBezier.quadTo(-x, 0, -x, -mRadiusSmall);
    mPathBezier.lineTo(-x, mRadiusSmall);
    mPathBezier.close();
  }

  @Override
  protected void onAttachedToWindow() {
    super.onAttachedToWindow();
    if (!mValueAnimator.isRunning())
      mValueAnimator.start();
  }

  @Override
  protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();
    if (mValueAnimator.isRunning())
      mValueAnimator.cancel();
  }
}

感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!

(0)

相关推荐

  • Android自定义控件实现随手指移动的小球

    一个关于自定义控件的小Demo,随着手指移动的小球. 先看下效果图: 实现代码如下: 1.自定义控件类 package com.dc.customview.view; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import

  • Android实现移动小球和CircularReveal页面切换动画实例代码

    前言 本文主要给大家介绍了关于Android如何实现移动小球和CircularReveal页面切换动画的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 效果图如下 是在fragment中跳转activity实现的效果,fragment跳fragment,activity跳activity类似~~ 实现过程 重写FloatingActionButton的onTouchListener()方法,使小球可以移动,并判断边界 点击fab时记录坐标传到下一个页面,在下一个页面展

  • Android动画之小球拟合动画实例

    Android动画之小球拟合动画实例 实现效果: 动画组成: 1.通过三阶贝塞尔曲线来拟合圆,拟合系数的由来,以及怎么选控制点. 2.利用画布canvas.translate,以及scale,rotate的方法,来渐变绘制的过程. 3.熟悉拟合过程. 4.不熟悉的话,先绘制辅助点的移动路线,对理解两个圆的分裂的拟合过程有好处. package com.example.administrator.animationworkdemo.views; import android.animation.V

  • Android中Property Animation属性动画编写的实例教程

    1.概述 Android提供了几种动画类型:View Animation .Drawable Animation .Property Animation .View Animation相当简单,不过只能支持简单的缩放.平移.旋转.透明度基本的动画,且有一定的局限性.比如:你希望View有一个颜色的切换动画:你希望可以使用3D旋转动画:你希望当动画停止时,View的位置就是当前的位置:这些View Animation都无法做到.这就是Property Animation产生的原因,本篇则来详细介绍

  • Android动画之补间动画(Tween Animation)实例详解

    本文实例讲述了Android动画之补间动画.分享给大家供大家参考,具体如下: 前面讲了<Android动画之逐帧动画(Frame Animation)>,今天就来详细讲解一下Tween动画的使用. 同样,在开始实例演示之前,先引用官方文档中的一段话: Tween动画是操作某个控件让其展现出旋转.渐变.移动.缩放的这么一种转换过程,我们称为补间动画.我们可以以XML形式定义动画,也可以编码实现. 如果以XML形式定义一个动画,我们按照动画的定义语法完成XML,并放置于/res/anim目录下,文

  • Android动画之逐帧动画(Frame Animation)实例详解

    本文实例分析了Android动画之逐帧动画.分享给大家供大家参考,具体如下: 在开始实例讲解之前,先引用官方文档中的一段话: Frame动画是一系列图片按照一定的顺序展示的过程,和放电影的机制很相似,我们称为逐帧动画.Frame动画可以被定义在XML文件中,也可以完全编码实现. 如果被定义在XML文件中,我们可以放置在/res下的anim或drawable目录中(/res/[anim | drawable]/filename.xml),文件名可以作为资源ID在代码中引用:如果由完全由编码实现,我

  • Android 带有弹出收缩动画的扇形菜单实例

    最近试着做了个Android 带有弹出收缩动画的扇形菜单,留个笔记记录一下. 效果如下 public class MainActivity extends AppCompatActivity implements View.OnClickListener { private ImageView imgPublish; private TextView textView1; private TextView textView2; private boolean isMenuOpen = false

  • Android 三种动画详解及简单实例

    Android 三种动画详解 帧动画 一张张图片不断的切换,形成动画效果 在drawable目录下定义xml文件,子节点为animation-list,在这里定义要显示的图片和每张图片的显示时长 <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="

  • 通过FancyView提供 Android 酷炫的开屏动画实例代码

    效果 使用 compile 'site.gemus:openingstartanimation:1.0.0' //在gradle中导入项目 OpeningStartAnimation openingStartAnimation = new OpeningStartAnimation.Builder(this) .setDrawStategy(new NormalDrawStrategy()) //设置动画效果 .create(); openingStartAnimation.show(this)

  • Android自定义View实现气泡动画

    本文实例为大家分享了Android自定义View实现气泡动画的具体代码,供大家参考,具体内容如下 一.前言 最近有需求制作一个水壶的气泡动画,首先在网上查找了一番,找到了一个文章:Android实现气泡动画 测试了一下发现,如果把它作为子视图的话,会出现小球溢出边界的情况.所以简单的修改了一下. 二.代码 1. 随机移动的气泡 Ball类 /** * @author jiang yuhang * @date 2021-04-18 19:57 */ class Ball { // 半径 @kotl

  • Android开发简单实现摇动动画的方法

    本文实例讲述了Android开发简单实现摇动动画的方法.分享给大家供大家参考,具体如下: 1.先创建shake.xml <?xml version="1.0" encoding="utf-8"?> <translate xmlns:android="http://schemas.android.com/apk/res/android" android:duration="700" android:fromXD

  • Android自定义加载圈动画效果

    本文实例为大家分享了Android自定义加载圈动画展示的具体代码,供大家参考,具体内容如下 实现如下效果: 该效果图主要有3个动画: 1.旋转动画 2.聚合动画 3.扩散动画 以上3个动画都是通过ValueAnimator来实现,配合自定义View的onDraw()方法实现不断的刷新和绘制界面. 具体代码如下: package blog.csdn.net.mchenys.myanimationloading; import android.animation.Animator; import a

随机推荐