转载请注明出处:http://blog.csdn.net/fightlei/article/details/52556755

在网上看到了一个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进度条就实现了!赶快运行,看看效果吧!

如有纰漏,敬请海涵。

自定义View实现钟摆效果进度条PendulumView的更多相关文章

  1. 自定义VIew——漂亮的圆形进度条

    package com.example.firstapp; import java.text.DecimalFormat; import android.annotation.SuppressLint ...

  2. Android 自定义view --圆形百分比(进度条)

    转载请注明出处:http://blog.csdn.net/wingichoy/article/details/50334595 注:本文由于是在学习过程中写的,存在大量问题(overdraw onDr ...

  3. 自定义view的drawRoundRect模拟进度条

    主要方法发介绍 1:drawRoundRect参数介绍 drawRoundRect(l,t,r,b,rx,ry,paint)里面的参数可以有两种: 1:前四个参数(l,t,r,,b)分别是矩形左边距离 ...

  4. 自定义仿 QQ 健康计步器进度条

    自定义仿 QQ 健康计步器进度条 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:CircleProgress 文中如有纰漏,欢迎大家留言指出. 闲着没事,趁上班时间偷偷撸了 ...

  5. 简直要逆天!超炫的 HTML5 粒子效果进度条

    我喜欢粒子效果作品,特别是那些能够应用于实际的,例如这个由 Jack Rugile 基于 HTML5 Cavnas 编写的进度条效果.看着这么炫的 Loading 效果,即使让我多等一会也无妨:)你呢 ...

  6. 我的Android进阶之旅------>Android如何通过自定义SeekBar来实现视频播放进度条

    首先来看一下效果图,如下所示: 其中进度条如下: 接下来说一说我的思路,上面的进度拖动条有自定义的Thumb,在Thumb正上方有一个PopupWindow窗口,窗口里面显示当前的播放时间.在Seek ...

  7. Android 自定义View跑马灯效果(一)

    今天通过书籍重新复习了一遍自定义VIew,为了加强自己的学习,我把它写在博客里面,有兴趣的可以看一下,相互学习共同进步: 通过自定义一个跑马灯效果,来诠释一下简单的效果: 一.创建一个类继承View, ...

  8. 利用贝塞尔曲线绘制(UIBezierPath)自定义iOS动态速度表,可以自定义刻度,刻度值,进度条样式

    GitHub的Demo下载地址 使用UIBezierPath画图步骤: 创建一个UIBezierPath对象 调用-moveToPoint:设置初始线段的起点 添加线或者曲线去定义一个或者多个子路径 ...

  9. 超炫的HTML5粒子效果进度条 VS 如何规范而优雅地code

    最近瞎逛的时候发现了一个超炫的粒子进度效果,有多炫呢?请擦亮眼镜!   // _this.ch){ _this.particles.splice(i, 1); } }; this.Particle.p ...

随机推荐

  1. JAVA的StringBuffer类[转]

    StringBuffer类和String一样,也用来代表字符串,只是由于StringBuffer的内部实现方式和String不同,所以StringBuffer在进行字符串处理时,不生成新的对象,在内存 ...

  2. python3--多目录之间的协作的一些必备知识

    # Auther: Aaron Fan # 动态获取执行文件的相对路径路径:print(__file__) #动态获取执行文件的绝对路径:import osfile_path = os.path.ab ...

  3. Java 前台后台数据传递、中文乱码解决方法

    1.向前台传递数据;2.向后台传递数据;3.ajax post 提交数据到服务端时中文乱码解决方法;4.数组类型参数传递; 1.向前台传递数据:1.1 字符串数据传递:  这种方式只是单一的向前台传递 ...

  4. windows cmd命令相关知识和经验的碎片化记录

    1.循环遍历当前文件夹下的所有*.dll文件,并打印其绝对路径和相对路径 ``` for /f "tokens=*" %%a in ('dir /s/b/a-d "*.d ...

  5. css中的三种基本定位机制

    css中的三种基本定位机制 a.普通文档流 b.定位:相对定位 绝对定位 固定定位 c.浮动 1.普通流中,元素位置由文档顺序和元素性质决定,块级元素从上到下依次排列,框之间的垂直距离由框的垂直mar ...

  6. Mac下在zsh中配置adb命令

    Mac下自带的终端默认黑白色的,对于一个技术宅来说不能忍啊.然后换成了iTerm,安装上了zsh,安装后界面如下: 这里写图片描述 但是常用的adb命令却找不到了,还向github上提了issue,下 ...

  7. 4.3.1 ThreadLoacl简单使用

    我们都知道  SimpleDateFormat 这个类是线程 不安全的,那么我下面的程序执行就会遇到问题 public class ParseDateDemo { private static fin ...

  8. eclipse 市场

    http://marketplace.eclipse.org/ 如何找工具官网? 举例:UMLet 进入官网以后,下载插件,然后离线安装.

  9. java软件托盘显示中文出现框框乱码

    目录 文章背景 目录 问题的代码 解决办法 说明 参考文章 版本记录 文章背景 测试代码时候突然发现代码运行时候的托盘菜单的名字显示异常,查询了下资料发现是运行参数的缘故. 目录 问题的代码 系统是w ...

  10. 【小梅哥FPGA进阶教程】第十四章 TFT屏显示图片

    十四.TFT屏显示图片 本文由杭电网友曾凯峰贡献,特此感谢 学习了小梅哥的TFT显示屏驱动设计后,想着在此基础上通过TFT屏显示一张图片,有了这个想法就开始动工了.首先想到是利用FPGA内部ROM存储 ...