我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android自定义View实现钟摆效果进度条PendulumView

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android自定义View实现钟摆效果进度条PendulumView

在网上看到了一个IOS组件PendulumView,实现了钟摆的动画效果。由于原生的进度条确实是不好看,所以想可以自定义View实现这样的效果,以后也可以用于加载页面的进度条。 

废话不多说,先上效果图

 

底部黑边是录制时不小心录上的,可以忽略。 

既然是自定义View我们就按标准的流程来,第一步,自定义属性 

自定义属性 

建立属性文件 

在Android项目的res->values目录下新建一个attrs.xml文件,文件内容如下:


 <?xml version="1.0" encoding="utf-8"?>
<resources>
 <declare-styleable name="PendulumView">
  <attr name="globeNum" format="integer"/>
  <attr name="globeColor" format="color"/>
  <attr name="globeRadius" format="dimension"/>
  <attr name="swingRadius" format="dimension"/>
 </declare-styleable>
</resources>

其中declare-styleable的name属性用于在代码中引用该属性文件。name属性,一般情况下写的都是我们自定义View的类名,较为直观。

使用styleale,系统可以为我们完成很多常量(int[]数组,下标常量)等的编写,简化我们的开发工作,例如下面代码中用到的R.styleable.PendulumView_golbeNum等就是系统为我们自动生成的。 

globeNum属性表示小球数量,globeColor表示小球颜色,globeRadius表示小球半径,swingRadius表示摆动半径 

读取属性值 

在自定view的构造方法中通过TypedArray读取属性值 

通过AttributeSet同样可以获取属性值,但是如果属性值是引用类型,则得到的只是ID,仍需继续通过解析ID获取真正的属性值,而TypedArray直接帮助我们完成了上述工作。 


public PendulumView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    //使用TypedArray读取自定义的属性值
    TypedArray ta = context.getResources().obtainAttributes(attrs, R.styleable.PendulumView);
    int count = ta.getIndexCount();
    for (int i = 0; i < count; i++) {
      int attr = ta.getIndex(i);
      switch (attr) {
        case R.styleable.PendulumView_globeNum:
          mGlobeNum = ta.getInt(attr, 5);
          break;
        case R.styleable.PendulumView_globeRadius:
          mGlobeRadius = ta.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 16, getResources().getDisplayMetrics()));
          break;
        case R.styleable.PendulumView_globeColor:
          mGlobeColor = ta.getColor(attr, Color.BLUE);
          break;
        case R.styleable.PendulumView_swingRadius:
          mSwingRadius = ta.getDimensionPixelSize(attr, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 16, getResources().getDisplayMetrics()));
          break;
      }
    }
    ta.recycle(); //避免下次读取时出现问题
    mPaint = new Paint();
    mPaint.setColor(mGlobeColor);
  }

重写OnMeasure()方法 


@Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    //高度为小球半径+摆动半径
    int height = mGlobeRadius + mSwingRadius;
    //宽度为2*摆动半径+(小球数量-1)*小球直径
    int width = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1) + mSwingRadius;
    //如果测量模式为EXACTLY,则直接使用推荐值,如不为EXACTLY(一般处理wrap_content情况),使用自己计算的宽高
    setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize : width, (heightMode == MeasureSpec.EXACTLY) ? heightSize : height);
  }

其中
 int height = mGlobeRadius + mSwingRadius;
<pre name="code" class="java">int width = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1) + mSwingRadius;
用于处理测量模式为AT_MOST的情况,一般是自定义View的宽高设置为了wrap_content,此时通过小球的数量,半径,摆动的半径等计算View的宽高,如下图: 

以小球个数5为例,View的大小为下图红色矩形区域 

重写onDraw()方法 


@Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //绘制除左右两个小球外的其他小球
    for (int i = 0; i < mGlobeNum - 2; i++) {
      canvas.drawCircle(mSwingRadius + (i + 1) * 2 * mGlobeRadius, mSwingRadius, mGlobeRadius, mPaint);
    }
    if (mLeftPoint == null || mRightPoint == null) {
      //初始化最左右两小球坐标
      mLeftPoint = new Point(mSwingRadius, mSwingRadius);
      mRightPoint = new Point(mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1), mSwingRadius);
      //开启摆动动画
      startPendulumAnimation();
    }
    //绘制左右两小球
    canvas.drawCircle(mLeftPoint.x, mLeftPoint.y, mGlobeRadius, mPaint);
    canvas.drawCircle(mRightPoint.x, mRightPoint.y, mGlobeRadius, mPaint);
  }

onDraw()方法是自定义View的关键所在,在该方法体内绘制View的显示效果。代码首先绘制了除去最左边最右边小球以外的其他小球,然后对左右两小球的坐标值进行判断,如果是第一次绘制,坐标值均为空,则初始化两小球坐标,并且开启动画。最后通过mLeftPoint,mRightPoint的x,y值,绘制左右两个小球。 

其中mLeftPoint,mRightPoint均是android.graphics.Point对象,仅是使用它们来存放左右两小球的x,y坐标信息。 

使用属性动画 


public void startPendulumAnimation() {
    //使用属性动画
    final ValueAnimator anim = ValueAnimator.ofObject(new TypeEvaluator() {
      @Override
      public Object evaluate(float fraction, Object startValue, Object endValue) {
        //参数fraction用于表示动画的完成度,我们根据它来计算当前的动画值
        double angle = Math.toRadians(90 * fraction);
        int x = (int) ((mSwingRadius - mGlobeRadius) * Math.sin(angle));
        int y = (int) ((mSwingRadius - mGlobeRadius) * Math.cos(angle));
        Point point = new Point(x, y);
        return point;
      }
    }, new Point(), new Point());
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        Point point = (Point) animation.getAnimatedValue();
        //获得当前的fraction值
        float fraction = anim.getAnimatedFraction();
        //判断是否是fraction先减小后增大,即是否处于即将向上摆动状态
        //在每次即将向上摆动时切换小球
        if (lastSlope && fraction > mLastFraction) {
          isNext = !isNext;
        }
        //通过不断改动左右小球的x,y坐标值实现动画效果
        //利用isNext来判断应该是左边小球动,还是右边小球动
        if (isNext) {
          //当左边小球摆动时,右边小球置于初始位置
          mRightPoint.x = mSwingRadius + mGlobeRadius * 2 * (mGlobeNum - 1);
          mRightPoint.y = mSwingRadius;
          mLeftPoint.x = mSwingRadius - point.x;
          mLeftPoint.y = mGlobeRadius + point.y;
        } else {
          //当右边小球摆动时,左边小球置于初始位置
          mLeftPoint.x = mSwingRadius;
          mRightPoint.y = mSwingRadius;
          mRightPoint.x = mSwingRadius + (mGlobeNum - 1) * mGlobeRadius * 2 + point.x;
          mRightPoint.y = mGlobeRadius + point.y;
        }
        invalidate();
        lastSlope = fraction < mLastFraction;
        mLastFraction = fraction;
      }
    });
    //设置永久循环播放
    anim.setRepeatCount(ValueAnimator.INFINITE);
    //设置循环模式为倒序播放
    anim.setRepeatMode(ValueAnimator.REVERSE);
    anim.setDuration(200);
    //设置补间器,控制动画的变化速率
    anim.setInterpolator(new DecelerateInterpolator());
    anim.start();
  }

 其中使用ValueAnimator.ofObject方法是为了可以对Point对象进行操作,更为形象具体。还有就是通过ofObject方法使用了自定义的TypeEvaluator对象,由此得到了fraction值,该值是一个从0-1变化的小数。所以该方法的后两个参数startValue(new Point()),endValue(new Point())并没有实际意义,也可以直接不写,此处写上主要是为了便于理解。同样道理也可以直接使用ValueAnimator.ofFloat(0f, 1f)方法获取到一个从0-1变化的小数。


     final ValueAnimator anim = ValueAnimator.ofObject(new TypeEvaluator() {
      @Override
      public Object evaluate(float fraction, Object startValue, Object endValue) {
        //参数fraction用于表示动画的完成度,我们根据它来计算当前的动画值
        double angle = Math.toRadians(90 * fraction);
        int x = (int) ((mSwingRadius - mGlobeRadius) * Math.sin(angle));
        int y = (int) ((mSwingRadius - mGlobeRadius) * Math.cos(angle));
        Point point = new Point(x, y);
        return point;
      }
    }, new Point(), new Point());

通过fraction,我们计算得到小球摆动时的角度变化值,0-90度

 

mSwingRadius-mGlobeRadius表示的值是图中绿色直线的长度,摆动的路线,小球圆心的路线是一个以(mSwingRadius-mGlobeRadius)为半径的弧线,变化的X值为(mSwingRadius-mGlobeRadius)*sin(angle),变化的y值为(mSwingRadius-mGlobeRadius)*cos(angle) 

对应的小球实际的圆心坐标为(mSwingRadius-x,mGlobeRadius+y) 

右边小球运动路线与左边类似,仅仅是方向不同。右边小球实际的圆心坐标(mSwingRadius + (mGlobeNum - 1) * mGlobeRadius * 2 + x,mGlobeRadius+y) 

可见左右两边小球的纵坐标是相同的,仅横坐标不同。 


        float fraction = anim.getAnimatedFraction();
        //判断是否是fraction先减小后增大,即是否处于即将向上摆动状态
        //在每次即将向上摆动时切换小球
        if (lastSlope && fraction > mLastFraction) {
          isNext = !isNext;
        }
        //记录上一次fraction是否不断减小
        lastSlope = fraction < mLastFraction;
        //记录上一次的fraction
        mLastFraction = fraction;

 这两段代码用于计算何时切换运动的小球,本动画设置了循环播放,且循环模式为倒序播放,所以动画的一个周期即为小球抛起加上小球落下的过程。在该过程中fraction的值先有0变为1,再由1变为0。那么何时是动画新一轮周期的开始呢?就是在小球即将抛起的时候,在这个时候切换运动的小球,即可实现左边小球落下后右边小球抛起,右边小球落下后左边小球抛起的动画效果。 

那么如何捕捉到这个时间点呢? 

小球抛起时fraction值不断增大,小球落下时fraction值不断减小。小球即将抛起的时刻,就是fraction从不断减小转变为不断增大的时刻。代码中记录上一次fraction是否在不断减小,然后比较这一次fraction是否在不断增大,若两个条件均成立则切换运动的小球。 


    anim.setDuration(200);
    //设置补间器,控制动画的变化速率
    anim.setInterpolator(new DecelerateInterpolator());
    anim.start();

设置动画的持续时间为200毫秒,读者可以通过更改该值而达到修改小球摆动速度的目的。

设置动画的补间器,由于小球抛起是一个逐渐减速的过程,落下是一个逐渐加速的过程,所以使用DecelerateInterpolator实现减速效果,在倒序播放时为加速效果。 

启动动画,钟摆效果的自定义View进度条就实现了!赶快运行,看看效果吧!

您可能感兴趣的文章:Android view自定义实现动态进度条Android 自定义view和属性动画实现充电进度条效果Android自定义View基础开发之图片加载进度条Android自定义View之圆形进度条式按钮Android自定义View实现带数字的进度条实例代码android自定义进度条渐变色View的实例代码Android实现环形进度条的实例Android自定义漂亮的圆形进度条Android实现环形进度条代码Android 仿支付宝中的余额宝收益进度条Android自定义带水滴的进度条样式(带渐变色效果)Android编程基于自定义View实现绚丽的圆形进度条功能示例


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android自定义View实现钟摆效果进度条PendulumView

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android自定义View实现钟摆效果进度条PendulumView

在网上看到了一个IOS组件PendulumView,实现了钟摆的动画效果。由于原生的进度条确实是不好看,所以想可以自定义View实现这样的效果,以后也可以用于加载页面的进度条。 废话不多说,先上效果图 底部黑边是录制时不小心录上的,可以忽
2022-06-06

Android自定义view实现进度条指示效果

先看看效果图:首先是布局文件
2022-06-06

Android自定义Animation实现View摇摆效果

使用自定义Animation,实现View的左右摇摆效果,如图所示:代码很简单,直接上源码 activity_maini.xml布局文件:
2022-06-06

android自定义view实现钟表效果

本文实例为大家分享了android view实现钟表的具体代码,供大家参考,具体内容如下 先看效果图:自定义view大家肯定已经不陌生了,所以直接今天直接步入正题:如何利用canvas去绘制出一个钟表 当然绘制之前我们必须进行测量(重写on
2022-06-06

Android中怎么自定义view实现圆环进度条效果

这篇文章主要讲解了“Android中怎么自定义view实现圆环进度条效果”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Android中怎么自定义view实现圆环进度条效果”吧!核心代码自定义
2023-06-29

android自定义view制作圆形进度条效果

还是我们自定View的那几个步骤: 1、自定义View的属性 2、在View的构造方法中获得我们自定义的属性 [ 3、重写onMesure ] 4、重写onDraw1、自定义属性:2022-06-06

Android view自定义实现动态进度条

Android 自定义view实现动态进度条 效果图:这个是看了梁肖的demo,根据他的思路自己写了一个,但是我写的这个貌似计算还是有些问题,从上面的图就可以看出来,左侧、顶部、右侧的线会有被截掉的部分,有懂得希望能给说一下,改进一下,这
2022-06-06

Android自定义Material进度条效果

首先看下效果图布局文件:2022-06-06

Android中怎么通过自定义view实现进度条加载效果

Android中怎么通过自定义view实现进度条加载效果,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。分析图:代码如下:package com.example.dotloa
2023-05-30

Android自定义View实现圆形加载进度条效果的方法

这篇文章将为大家详细讲解有关Android自定义View实现圆形加载进度条效果的方法,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。View仿华为圆形加载进度条效果图实现思路可以看出该View可分为三个部分
2023-05-30

Android怎么自定义View实现圆弧进度效果

这篇文章主要介绍“Android怎么自定义View实现圆弧进度效果”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Android怎么自定义View实现圆弧进度效果”文章能帮助大家解决问题。技术实现1.
2023-07-06

Android自定义view实现水波纹进度球效果

今天我们要实现的这个view没有太多交互性的view,所以就继承view。 自定义view的套路,套路很深 1、获取我们自定义属性attrs(可省略) 2、重写onMeasure方法,计算控件的宽和高 3、重
2022-06-06

Android 自定义view和属性动画实现充电进度条效果

近期项目中需要使用到一种类似手机电池充电进度的动画效果,以前没学属性动画的时候,是用图片+定时器的方式来完成的,最近一直在学习动画这一块,再加上复习一下自定义view的相关知识点,所以打算用属性动画和自定义view的方式来完成这个功能,将它
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录