热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android实现笑脸进度加载动画

这篇文章主要介绍了Android实现笑脸进度加载动画的方法,帮助大家更好的理解和学习使用Android,感兴趣的朋友可以了解下

最近看到豆瓣的笑脸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) {

            }
        });
    }
}

自定义属性


        
        
        

布局文件中使用

完整代码都在上面啦.

到这里就结束啦.

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


推荐阅读
  • Android 九宫格布局详解及实现:人人网应用示例
    本文深入探讨了人人网Android应用中独特的九宫格布局设计,解析其背后的GridView实现原理,并提供详细的代码示例。这种布局方式不仅美观大方,而且在现代Android应用中较为少见,值得开发者借鉴。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • Linux 系统启动故障排除指南:MBR 和 GRUB 问题
    本文详细介绍了 Linux 系统启动过程中常见的 MBR 扇区和 GRUB 引导程序故障及其解决方案,涵盖从备份、模拟故障到恢复的具体步骤。 ... [详细]
  • 本文介绍了如何使用jQuery根据元素的类型(如复选框)和标签名(如段落)来获取DOM对象。这有助于更高效地操作网页中的特定元素。 ... [详细]
  • 本文将详细介绍如何使用剪映应用中的镜像功能,帮助用户轻松实现视频的镜像效果。通过简单的步骤,您可以快速掌握这一实用技巧。 ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 本文介绍如何在 Xcode 中使用快捷键和菜单命令对多行代码进行缩进,包括右缩进和左缩进的具体操作方法。 ... [详细]
  • 本文介绍了一款用于自动化部署 Linux 服务的 Bash 脚本。该脚本不仅涵盖了基本的文件复制和目录创建,还处理了系统服务的配置和启动,确保在多种 Linux 发行版上都能顺利运行。 ... [详细]
  • 在Linux系统中配置并启动ActiveMQ
    本文详细介绍了如何在Linux环境中安装和配置ActiveMQ,包括端口开放及防火墙设置。通过本文,您可以掌握完整的ActiveMQ部署流程,确保其在网络环境中正常运行。 ... [详细]
  • Android 渐变圆环加载控件实现
    本文介绍了如何在 Android 中创建一个自定义的渐变圆环加载控件,该控件已在多个知名应用中使用。我们将详细探讨其工作原理和实现方法。 ... [详细]
  • 如何在WPS Office for Mac中调整Word文档的文字排列方向
    本文将详细介绍如何使用最新版WPS Office for Mac调整Word文档中的文字排列方向。通过这些步骤,用户可以轻松更改文本的水平或垂直排列方式,以满足不同的排版需求。 ... [详细]
  • 本文总结了在使用Ionic 5进行Android平台APK打包时遇到的问题,特别是针对QRScanner插件的改造。通过详细分析和提供具体的解决方法,帮助开发者顺利打包并优化应用性能。 ... [详细]
  • 理解存储器的层次结构有助于程序员优化程序性能,通过合理安排数据在不同层级的存储位置,提升CPU的数据访问速度。本文详细探讨了静态随机访问存储器(SRAM)和动态随机访问存储器(DRAM)的工作原理及其应用场景,并介绍了存储器模块中的数据存取过程及局部性原理。 ... [详细]
  • 360SRC安全应急响应:从漏洞提交到修复的全过程
    本文详细介绍了360SRC平台处理一起关键安全事件的过程,涵盖从漏洞提交、验证、排查到最终修复的各个环节。通过这一案例,展示了360在安全应急响应方面的专业能力和严谨态度。 ... [详细]
  • 几何画板展示电场线与等势面的交互关系
    几何画板是一款功能强大的物理教学软件,具备丰富的绘图和度量工具。它不仅能够模拟物理实验过程,还能通过定量分析揭示物理现象背后的规律,尤其适用于难以在实际实验中展示的内容。本文将介绍如何使用几何画板演示电场线与等势面之间的关系。 ... [详细]
author-avatar
SREFLANKCWF
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有