Android实现笑脸进度加载动画

编辑: admin 分类: Android 发布时间: 2021-11-29 来源:互联网
目录
  • 一、默认状态
    • 1、画嘴巴
    • 2、画眼睛
  • 二、合并状态
    • 1、嘴巴的旋转
    • 2、眼睛的旋转
  • 三、自转状态
    • 1、开启动画
    • 2、重新绘制
  • 四、分离状态
    • 最后附上完整代码

      最近看到豆瓣的笑脸loading很有意思,看一张效果图:

      下面分析一下如何实现这样的效果:

      1、默认状态是一张笑脸的状态(一个嘴巴,两个眼睛,默认状态)

      2、开始旋转,嘴巴追上眼睛(合并状态)

      3、追上以后自转一周(自转状态)

      4、然后逐渐释放眼睛(分离状态)

      5、回到初始笑脸状态(默认状态)

      一、默认状态

      首先需要确定好嘴巴和眼睛的初始位置,我这里的初始化嘴巴是一个半圆,在横轴下方。眼睛分别与横轴夹角60度,如下图:

      这两部分可以使用pathMeasure,我这里使用最简单的两个api:canvas.drawArc()和canvas.drawPoint()。

      1、画嘴巴

       //画起始笑脸
      canvas.drawArc(-radius, -radius, radius, radius, startAngle, swipeAngle, false,facePaint);

      这里的startAngle初始值为0,swiperAngle为180,半径radius为40。

      2、画眼睛

      (1)初始化眼睛坐标

         /**
           * 初始化眼睛坐标
           */
          private void initEyes() {
              //默认两个眼睛坐标位置 角度转弧度
              leftEyeX = (float) (-radius * Math.cos(eyeStartAngle * Math.PI / 180));
              leftEyeY = (float) (-radius * Math.sin(eyeStartAngle * Math.PI / 180));
              rightEyeX = (float) (radius * Math.cos(eyeStartAngle * Math.PI / 180));
              rightEyeY = (float) (-radius * Math.sin(eyeStartAngle * Math.PI / 180));
          }

      注意:需要将角度转弧度

      (2)开始画眼睛

         //画起始眼睛
        canvas.drawPoint(leftEyeX, leftEyeY, eyePaint);
        canvas.drawPoint(rightEyeX, rightEyeY, eyePaint);

      二、合并状态

      这个状态可以分为两部分

      • 嘴巴的旋转
      • 眼睛的旋转

      1、嘴巴的旋转

      开启动画

           faceLoadingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(1000);
           faceLoadingAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
           faceLoadingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                  @Override
                  public void onAnimationUpdate(ValueAnimator animation) {
                      faceValue = (float) animation.getAnimatedValue();
                      invalidate();
                  }
              });
              //动画延迟500ms启动
              faceLoadingAnimator.setStartDelay(200);
      
              faceLoadingAnimator.addListener(new Animator.AnimatorListener() {
                  @Override
                  public void onAnimationStart(Animator animation) {
      
                  }
      
                  @Override
                  public void onAnimationEnd(Animator animation) {
                      //恢复起始状态
                      currentStatus = smileStatus;
                  }
      
                  @Override
                  public void onAnimationCancel(Animator animation) {
      
                  }
      
                  @Override
                  public void onAnimationRepeat(Animator animation) {
      
                  }
              });

      动画执行时间1s,记录动画当前执行进度值,存放在faceValue中。当动画执行结束的时候,需要将状态恢复到默认状态,调用invalidate的时候,进入onDraw()方法,开始重新绘制嘴巴。

                      //记录时刻的旋转角度
                      startAngle = faceValue * 360;
      
      
                      //追上右边眼睛
                      if (startAngle >= 120 + startAngle / 2) {
      
                          canvas.drawArc(-radius, -radius, radius, radius, startAngle,
                                  swipeAngle, false, facePaint);
      
                          //开始自转一圈
                          mHandler.sendEmptyMessage(2);
      
                          //此时记录自转一圈起始的角度
                          circleStartAngle = 120 + startAngle / 2;
      
                      } else {
      
                          //追眼睛的过程
                          canvas.drawArc(-radius, -radius, radius, radius, startAngle,
                                  swipeAngle, false, facePaint);
      
                      }

      这里的每次旋转角度为startAngle。当完全追赶上右侧眼睛的时候,开始执行自转一周,并停止当前动画。

      2、眼睛的旋转

      眼睛的开始旋转速度明显是慢于嘴巴的旋转速度,所以每次的旋转速度可以设置为嘴巴的一半

        //画左边眼睛 ,旋转的角度设置为笑脸旋转角度的一半,这样笑脸才能追上眼睛
        leftEyeX = (float) (-radius * Math.cos((60 + startAngle / 2) * Math.PI / 180));
        leftEyeY = (float) (-radius * Math.sin((60 + startAngle / 2) * Math.PI / 180));
        canvas.drawPoint(leftEyeX, leftEyeY, eyePaint);
      
        //画右边眼睛 ,旋转的角度设置为笑脸旋转角度的一半,这样笑脸才能追上眼睛
        rightEyeX = (float) (radius * Math.cos((60 - startAngle / 2) * Math.PI / 180));
        rightEyeY = (float) (-radius * Math.sin((60 - startAngle / 2) * Math.PI / 180));
        canvas.drawPoint(rightEyeX, rightEyeY, eyePaint);

      三、自转状态

      1、开启动画

              circleAnimator = ValueAnimator.ofFloat(0, 1).setDuration(1000);
      
              circleAnimator.setInterpolator(new LinearInterpolator());
      
              circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                  @Override
                  public void onAnimationUpdate(ValueAnimator animation) {
                      circleValue = (float) animation.getAnimatedValue();
                      invalidate();
                  }
              });
      
              circleAnimator.addListener(new Animator.AnimatorListener() {
                  @Override
                  public void onAnimationStart(Animator animation) {
                  }
      
                  @Override
                  public void onAnimationEnd(Animator animation) {
                      mHandler.sendEmptyMessage(3);
                  }
      
                  @Override
                  public void onAnimationCancel(Animator animation) {
      
                  }
      
                  @Override
                  public void onAnimationRepeat(Animator animation) {
      
                  }
              });

      2、重新绘制

       canvas.drawArc(-radius, -radius, radius, radius,
                              circleStartAngle + circleValue * 360,
                              swipeAngle, false, facePaint);

      四、分离状态

      主要的注意点就是眼睛的旋转角度设置为嘴巴旋转角度的2倍,这样才会达到眼睛超过嘴巴的效果,主要的旋转代码如下:

                      startAngle = faceValue * 360;
                      //判断当前笑脸的起点是否已经走过260度 (吐出眼睛的角度,角度可以任意设置)
                      if (startAngle >= splitAngle) {
      
                          //画左边眼睛 ,旋转的角度设置为笑脸旋转角度的2倍,这样眼睛才能快于笑脸旋转速度
                          leftEyeX = (float) (-radius * Math.cos((eyeStartAngle + startAngle * 2) * Math.PI / 180));
                          leftEyeY = (float) (-radius * Math.sin((eyeStartAngle + startAngle * 2) * Math.PI / 180));
                          canvas.drawPoint(leftEyeX, leftEyeY, eyePaint);
      
                          //画右边眼睛 ,旋转的角度设置为笑脸旋转角度的2倍,这样眼睛才能快于笑脸旋转速度
                          rightEyeX = (float) (radius * Math.cos((eyeStartAngle - startAngle * 2) * Math.PI / 180));
                          rightEyeY = (float) (-radius * Math.sin((eyeStartAngle - startAngle * 2) * Math.PI / 180));
                          canvas.drawPoint(rightEyeX, rightEyeY, eyePaint);
      
                      }
                      //画笑脸
                      canvas.drawArc(-radius, -radius, radius, radius, startAngle, swipeAngle,
                              false, facePaint);

      最后附上完整代码

      public class FaceView2 extends View {
      
      
          //圆弧半径
          private int radius = 40;
      
          //圆弧画笔宽度
          private float paintWidth = 15;
      
          //笑脸状态(一个脸,两个眼睛)
          private final int smileStatus = 0;
      
          //加载状态 合并眼睛,旋转
          private final int loadingStatus = 1;
      
          //合并完成 转一圈
          private final int circleStatus = 2;
      
          //转圈完成 吐出眼睛
          private final int splitStatus = 3;
      
          //当前状态
          private int currentStatus = smileStatus;
      
          //笑脸画笔
          private Paint facePaint;
          //眼睛画笔
          private Paint eyePaint;
      
          //笑脸开始角度
          private float startAngle;
          //笑脸弧度
          private float swipeAngle;
      
          //左侧眼睛起点x轴坐标
          private float leftEyeX = 0;
          //左侧眼睛起点y轴坐标
          private float leftEyeY = 0;
      
          //右侧眼睛起点x轴坐标
          private float rightEyeX;
          //右侧眼睛起点y轴坐标
          private float rightEyeY;
      
          //一开始默认状态笑脸转圈动画
          private ValueAnimator faceLoadingAnimator;
      
          //吞并完成后,自转一圈动画
          private ValueAnimator circleAnimator;
      
          //faceLoadingAnimator动画进度值
          private float faceValue;
      
          //circleAnimator动画进度值
          private float circleValue;
      
          //记录开始自转一圈的起始角度
          private float circleStartAngle;
      
          //吐出眼睛的角度
          private float splitAngle;
      
          private float initStartAngle;
      
          //眼睛起始角度
          private float eyeStartAngle = 60;
      
          public FaceView2(Context context) {
              this(context, null);
          }
      
          public FaceView2(Context context, AttributeSet attrs) {
              this(context, attrs, 0);
          }
      
          public FaceView2(Context context, AttributeSet attrs,
                           int defStyleAttr) {
              super(context, attrs, defStyleAttr);
      
              //自定义属性
              TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FaceView2,
                      defStyleAttr, 0);
      
              initStartAngle = typedArray.getFloat(R.styleable.FaceView2_startAngle, 0);
              swipeAngle = typedArray.getFloat(R.styleable.FaceView2_swipeAngle, 180);
              splitAngle = typedArray.getFloat(R.styleable.FaceView2_splitAngle, 260);
      
              typedArray.recycle();
      
              startAngle = initStartAngle;
              eyeStartAngle += startAngle;
      
              initEyes();
      
              initPaint();
      
              //开始默认动画
              initAnimator();
      
          }
      
          /**
           * 初始化画笔
           */
          private void initPaint() {
              //初始化画笔
              facePaint = new Paint();
              facePaint.setStrokeWidth(paintWidth);
              facePaint.setColor(Color.RED);
              facePaint.setAntiAlias(true);
              facePaint.setStyle(Paint.Style.STROKE);
              facePaint.setStrokeCap(Paint.Cap.ROUND);
      
              eyePaint = new Paint();
              eyePaint.setStrokeWidth(paintWidth);
              eyePaint.setColor(Color.RED);
              eyePaint.setAntiAlias(true);
              eyePaint.setStyle(Paint.Style.STROKE);
              eyePaint.setStrokeCap(Paint.Cap.ROUND);
      
          }
      
          /**
           * 初始化眼睛坐标
           */
          private void initEyes() {
              //默认两个眼睛坐标位置 角度转弧度
              leftEyeX = (float) (-radius * Math.cos(eyeStartAngle * Math.PI / 180));
              leftEyeY = (float) (-radius * Math.sin(eyeStartAngle * Math.PI / 180));
              rightEyeX = (float) (radius * Math.cos(eyeStartAngle * Math.PI / 180));
              rightEyeY = (float) (-radius * Math.sin(eyeStartAngle * Math.PI / 180));
      
          }
      
          private Handler mHandler = new Handler(new Handler.Callback() {
              @RequiresApi(api = Build.VERSION_CODES.KITKAT)
              @Override
              public boolean handleMessage(Message msg) {
                  switch (msg.what) {
                      case 1:
                          //启动一开始笑脸转圈动画,并且开始合并眼睛
                          currentStatus = loadingStatus;
                          faceLoadingAnimator.start();
                          break;
      
                      case 2:
                          //暂停眼睛和笑脸动画
                          currentStatus = circleStatus;
                          faceLoadingAnimator.pause();
                          //启动笑脸自转一圈动画
                          circleAnimator.start();
                          break;
                      case 3:
                          //恢复笑脸转圈动画,并且开始分离眼睛
                          currentStatus = splitStatus;
                          circleAnimator.cancel();
                          faceLoadingAnimator.resume();
                          invalidate();
      
                          break;
                  }
                  return false;
              }
          });
      
          @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
          @Override
          protected void onDraw(Canvas canvas) {
              super.onDraw(canvas);
              //画布移到中间
              canvas.translate(getWidth() / 2, getHeight() / 2);
      
              switch (currentStatus) {
                  //起始状态
                  case smileStatus:
                      //起始角度为0
                      startAngle = initStartAngle;
      
                      //画起始笑脸
                      canvas.drawArc(-radius, -radius, radius, radius, startAngle, swipeAngle, false,
                              facePaint);
      
                      //重置起始眼睛坐标
                      initEyes();
      
                      //画起始眼睛
                      canvas.drawPoint(leftEyeX, leftEyeY, eyePaint);
                      canvas.drawPoint(rightEyeX, rightEyeY, eyePaint);
      
                      //更改状态,进行笑脸合并眼睛
                      mHandler.sendEmptyMessage(1);
                      break;
      
                  //合并状态
                  case loadingStatus:
      
                      //记录时刻的旋转角度
                      startAngle = faceValue * 360;
      
      
                      //追上右边眼睛
                      if (startAngle >= 120 + startAngle / 2) {
      
                          canvas.drawArc(-radius, -radius, radius, radius, startAngle,
                                  swipeAngle, false, facePaint);
      
                          //开始自转一圈
                          mHandler.sendEmptyMessage(2);
      
                          //此时记录自转一圈起始的角度
                          circleStartAngle = 120 + startAngle / 2;
      
                      } else {
      
                          //追眼睛的过程
                          canvas.drawArc(-radius, -radius, radius, radius, startAngle,
                                  swipeAngle, false, facePaint);
      
                      }
      
                      //画左边眼睛 ,旋转的角度设置为笑脸旋转角度的一半,这样笑脸才能追上眼睛
                      leftEyeX = (float) (-radius * Math.cos((60 + startAngle / 2) * Math.PI / 180));
                      leftEyeY = (float) (-radius * Math.sin((60 + startAngle / 2) * Math.PI / 180));
                      canvas.drawPoint(leftEyeX, leftEyeY, eyePaint);
      
                      //画右边眼睛 ,旋转的角度设置为笑脸旋转角度的一半,这样笑脸才能追上眼睛
                      rightEyeX = (float) (radius * Math.cos((60 - startAngle / 2) * Math.PI / 180));
                      rightEyeY = (float) (-radius * Math.sin((60 - startAngle / 2) * Math.PI / 180));
                      canvas.drawPoint(rightEyeX, rightEyeY, eyePaint);
                      break;
      
                  //自转一圈状态 circleValue * 360 为旋转角度
                  case circleStatus:
                      canvas.drawArc(-radius, -radius, radius, radius,
                              circleStartAngle + circleValue * 360,
                              swipeAngle, false, facePaint);
                      break;
      
                  //笑脸眼睛分离状态
                  case splitStatus:
      
                      startAngle = faceValue * 360;
                      //判断当前笑脸的起点是否已经走过260度 (吐出眼睛的角度,角度可以任意设置)
                      if (startAngle >= splitAngle) {
      
                          //画左边眼睛 ,旋转的角度设置为笑脸旋转角度的2倍,这样眼睛才能快于笑脸旋转速度
                          leftEyeX = (float) (-radius * Math.cos((eyeStartAngle + startAngle * 2) * Math.PI / 180));
                          leftEyeY = (float) (-radius * Math.sin((eyeStartAngle + startAngle * 2) * Math.PI / 180));
                          canvas.drawPoint(leftEyeX, leftEyeY, eyePaint);
      
                          //画右边眼睛 ,旋转的角度设置为笑脸旋转角度的2倍,这样眼睛才能快于笑脸旋转速度
                          rightEyeX = (float) (radius * Math.cos((eyeStartAngle - startAngle * 2) * Math.PI / 180));
                          rightEyeY = (float) (-radius * Math.sin((eyeStartAngle - startAngle * 2) * Math.PI / 180));
                          canvas.drawPoint(rightEyeX, rightEyeY, eyePaint);
      
                      }
                      //画笑脸
                      canvas.drawArc(-radius, -radius, radius, radius, startAngle, swipeAngle,
                              false, facePaint);
      
                      break;
      
              }
          }
      
          /**
           * 初始化动画
           */
          private void initAnimator() {
      
      
              faceLoadingAnimator = ValueAnimator.ofFloat(0, 1).setDuration(1000);
              faceLoadingAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
              faceLoadingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                  @Override
                  public void onAnimationUpdate(ValueAnimator animation) {
                      faceValue = (float) animation.getAnimatedValue();
                      invalidate();
                  }
              });
              //动画延迟500ms启动
              faceLoadingAnimator.setStartDelay(200);
      
              faceLoadingAnimator.addListener(new Animator.AnimatorListener() {
                  @Override
                  public void onAnimationStart(Animator animation) {
      
                  }
      
                  @Override
                  public void onAnimationEnd(Animator animation) {
                      //恢复起始状态
                      currentStatus = smileStatus;
                  }
      
                  @Override
                  public void onAnimationCancel(Animator animation) {
      
                  }
      
                  @Override
                  public void onAnimationRepeat(Animator animation) {
      
                  }
              });
      
      
              circleAnimator = ValueAnimator.ofFloat(0, 1).setDuration(1000);
      
              circleAnimator.setInterpolator(new LinearInterpolator());
      
              circleAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                  @Override
                  public void onAnimationUpdate(ValueAnimator animation) {
                      circleValue = (float) animation.getAnimatedValue();
                      invalidate();
                  }
              });
      
              circleAnimator.addListener(new Animator.AnimatorListener() {
                  @Override
                  public void onAnimationStart(Animator animation) {
                  }
      
                  @Override
                  public void onAnimationEnd(Animator animation) {
                      mHandler.sendEmptyMessage(3);
                  }
      
                  @Override
                  public void onAnimationCancel(Animator animation) {
      
                  }
      
                  @Override
                  public void onAnimationRepeat(Animator animation) {
      
                  }
              });
          }
      }

      自定义属性

      <declare-styleable name="FaceView2">
              <attr name="startAngle" format="dimension" />
              <attr name="swipeAngle" format="dimension" />
              <attr name="splitAngle" format="dimension" />
      </declare-styleable>

      布局文件中使用

      <com.example.viewdemo.FaceView2
           android:layout_width="match_parent"
           android:layout_height="match_parent"/>

      完整代码都在上面啦.

      到这里就结束啦.

      以上就是Android实现笑脸进度加载动画的详细内容,更多关于Android 笑脸进度加载的资料请关注海外IDC网其它相关文章!

      【原URL 台湾大带宽服务器http://www.558idc.com/tw.html复制请保留原URL】