属性动画
属性动画是通过逐步改变像素值从而达到动画的效果。例如把view从左移到右,通过transformX逐步修改横向坐标值。其他如缩放,旋转,渐变同理。
ViewPropertyAnimator
viewPropertyAnimator属性动画是对于view来说的。任何其他的view都是继承view,包括viewGroup。view内有一个方法animate通过链式调用就可以达到属性动画的效果。它也是属性动画中比较简单实用的方法,但在自定义属性动画中,这种方法就不适合用了,原因就在于该链式调用的api有局限性,也就是不能对自定义属性进行动画,也就不能够满足我们自定义属性动画开发的需求。从而在自定义view中使用到的属性动画就不是选择ViewPropertyAnimator,而是选择另外的属性动画,后面会讲到。
view.animate()
.translationX(DisplayUtils.dpToPx(200))
.translationY(DisplayUtils.dpToPx(200))
.alpha(100)
.rotationX(100)
.rotationY(100)
.scaleX(100)
.scaleY(100)
.withStartAction(new Runnable() {
@Override
public void run() {
}
})
.withEndAction(new Runnable() {
@Override
public void run() {
}
})
.setStartDelay(1000)
.rotation(360)
.start();
ViewPropertyAnimator使用的都是些固定的动画和对动画进行监听等等,都能够看下源码就知道如何使用,大家可以查看下,我这里就不赘述了。
ObjectAnimator
ObjectAnimator属性动画可以通过自定义属性达到我们想要的属性动画效果,比如说我这里自定义一个圆,通过改变圆的半径来对圆进行缩放,我们就以这样的效果来举个简单的例子,让大家能够很清晰的对ObjectAnimator属性动画有一个清晰的认识。
第一步:自定义circleView继承view
第二步:初始化画笔和定义变量radius:
private float radius=DisplayUtils.dpToPx(50)
第三步:在ondraw方法中画圆:canvas.drawCircle(getWidth()/2,getHeight()/2,radius,paint);
简单的一个自定义view就算完了,接下来我们再来使用ObjectAnimator
这里大家可以看到我们定义的属性为radius,然而通过上面的截图,我们能够看到是有问题的,就是提示错误,提示的错误就是我们没有发现setRadius(float radius)这样的方法。我们再回到自定义CircleView处设置该方法,这里的错误提示就没有了。
public void setRadius(float radius){
this.radius = radius;
}
public float getRadius(){
return radius;
}
但是这里要注意,1.get和set方法都要写,2.虽然这里解决了错误提示的问题,但是当我们运行代码,这里是没有效果的,这又是为什么呢?原因就是我们没有刷新view,也就是在setRadius方法块内在设置完值后要进行刷新view的操作。也就是调用invalidate(),再次运行程序,属性动画效果就有了。
public void setRadius(float radius){
this.radius = radius;
invalidate();
}
public float getRadius(){
return radius;
}
这里是单个属性的使用案例,但在实际的开发过程中,一个view可能会涉及到多个属性动画。这里就要讲到自定义属性的另外一个针对多个属性动画的动画集合AnimatorSet
animatorSet
animatorSet是动画集合,比如一个view有多个动画,比如说有旋转动画,位移动画,缩放动画,以及各类不同参数下对应的动画的一个集合,我们这里可以选择使用animatorSet,同样为了更好的理解属性动画animatorset,我们可以举一个上篇文章最后讲到的camera几何变换中的例子展开,更深入的了解android中动画的使用。
定义三个属性变量分别对应camera的旋转角度,以及上下折叠的关于x轴方向的旋转角度flipRotation ,topFlip ,bottomFlip,并替换掉对应固定的值。然后根据上面讲解的,对这三个变量进行封装成get和set方法并在set方法赋完值后调用invalidate()
我们先来看下效果,这里我们先以bottomFlip属性为例,代码如下:
ObjectAnimator bottomFlipAnimator = ObjectAnimator.ofFloat(view,"bottomFlip",135);
bottomFlipAnimator.setStartDelay(1000);
bottomFlipAnimator.setDuration(1000);
bottomFlipAnimator.start();
由于我这里是使用的markdown,貌似不能插入视频,那么我们就以运行的结果来显示下。顺便提下,如果有读者知道如何在markdown下插入视频,看到这篇文章希望能够提供下解决方案。
运行结果如下:
接下来我们在把剩下的属性动画设置下并把三种不同的属性动画通过animatorSet.playSequentially添加到动画集合animatorSet中:
ObjectAnimator bottomFlipAnimator = ObjectAnimator.ofFloat(view,"bottomFlip",30);
bottomFlipAnimator.setDuration(1000);
ObjectAnimator TopFlipAnimator = ObjectAnimator.ofFloat(view,"topFlip",-30);
TopFlipAnimator.setDuration(1000);
ObjectAnimator flipRotationAnimator = ObjectAnimator.ofFloat(view,"flipRotation",270);
flipRotationAnimator.setDuration(1000);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(bottomFlipAnimator,flipRotationAnimator,TopFlipAnimator);
animatorSet.setStartDelay(1000);
animatorSet.start();
propertyValuelueHolder
animatorSet动画集合就是这样完成的,当然了除了animatorSet之外,其实ObjectAnimator也同样的提供了类似的api,比如说propertyValueHolder,接下来我们就以例子来看下propertyValuelueHolder的使用,其实也很好理解,第一个参数就是属性名称,第二个参数就是属性值。
PropertyValuesHolder bottomFlipHolder = PropertyValuesHolder.ofFloat("bottomFlip",30);
PropertyValuesHolder topFlipHolder = PropertyValuesHolder.ofFloat("topFlip",-30);
PropertyValuesHolder flipRotationHolder = PropertyValuesHolder.ofFloat("flipRotation",270);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(view,bottomFlipHolder,flipRotationHolder,topFlipHolder);
objectAnimator.setStartDelay(1000);
objectAnimator.setDuration(1000);
objectAnimator.start();
Keyframe
keyframe叫做关键帧,keyframe想必大家有可能没有接触过,但keyframe是相当好用的,比如你可以对某一个属性动画的效果能够更加符合你理想的效果,比如阻尼系数,平移动画每个时间段不同的运动效果,比如说在100s实现加速过后100s实现匀速最后100s实现减速等等都可以使用keyframe。同样举个简单例子,比如做一个平移动画,先加速然后减速最后加速的效果
float distance = DisplayUtils.dpToPx(300);
Keyframe keyframe1 = Keyframe.ofFloat(0,0);
Keyframe keyframe2 = Keyframe.ofFloat(0.1f,0.4f*distance);
Keyframe keyframe3 = Keyframe.ofFloat(0.9f,0.6f*distance);
Keyframe keyframe4 = Keyframe.ofFloat(1,distance);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("translationX",
keyframe1,keyframe2,keyframe3,keyframe4);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(imageView,holder);
animator.setStartDelay(1000);
animator.setDuration(1000);
animator.start();
interpolator
interpolator插值器我之前有一篇文章是专门讲解插值器的如果读者有兴趣的,可以转到我的另外一篇文章android动画(一)之插值器,这里我就不赘述了。
TypeEvaluator
上面讲解到的int或者float类型的属性动画,它们变化过程的每一个值都要被计算出来的,而帮我们计算的就是这里要讲到的TypeEvaluator。TypeEvaluator就是一个计算器,他会对指定类型的属性,精确的计算动画里面每一个属性值,这里的每一个属性值不是指的时间,而是指的动画完成度。
接下来我们实现一个类似于美团点餐的贝塞尔曲线效果,点击左上方的菜单,右下方就是购物车,由于没有图片这里用圆点和正方形表示下,布局文件如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".TypeEvaluatorActivity">
<com.zhaofan.property_animator.view.PointView
android:id="@+id/view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onStartAnimationClick"/>
<View
android:id="@+id/targetView"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:background="#000000"/>
</RelativeLayout>
由于我这里是讲解属性动画相关的知识点,对于贝塞尔曲线大家可以到百度搜索相关的文章。我这里是使用的二阶贝塞尔曲线,公式如下:
p0,p1,p2分别表示的是起始点,控制点和结束点,这里的t表示的是完成度
public void onStartAnimationClick(View view) {
//终点
int[] endPointLocation = new int[2];
targetView.getLocationOnScreen(endPointLocation);
Log.d(TAG, "outLocationX:" + endPointLocation[0] + "-----outLocationY" + endPointLocation[1]);
PointF endPointF = new PointF(endPointLocation[0], endPointLocation[1]);
controlPoint.set(endPointLocation[0] / 2f + offsetControlX, endPointLocation[1] / 2f - offsetControlY);
//起始点
PointF startPointF = new PointF(0, 0);
ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointFEvaluator(), startPointF, endPointF);
valueAnimator.setStartDelay(1000);
valueAnimator.setDuration(3000);
valueAnimator.start();
}
上面的代码主要就是定义起始点和终点以及控制点,接下来我们使用贝塞尔公式来计算view绘制的轨迹
private static class PointFEvaluator implements TypeEvaluator<PointF> {
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
//二阶贝塞尔曲线
Log.d(TAG, "startValueX:" + startValue.x + "---startValueY:" + startValue.y);
float x = (1 - fraction) * (1 - fraction) * startValue.x + 2 * fraction * (1 - fraction) * controlPoint.x + fraction * fraction * endValue.x;
float y = (1 - fraction) * (1 - fraction) * startValue.y + 2 * fraction * (1 - fraction) * controlPoint.y + fraction * fraction * endValue.y;
return new PointF(x, y);
}
}
然后通过实现addUpdateListener监听实现真正的动画操作。
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointF = (PointF) animation.getAnimatedValue();
pointView.setX(pointF.x);
pointView.setY(pointF.y);
}
});
硬件加速
软件绘制:用cpu来使用绘制代码,在软件层面就已经把所有像素都绘制好了,然后所有的像素数据都在你和屏幕交互前都已经准备好了,这种绘制方法叫软件绘制。
硬件绘制:使用cpu调用canvas相关的api,它们并不是绘制出来,而是把它们做初步的处理,转换成GPU操作。屏幕显示的时候,让GPU来绘制转换成实际的像素。
硬件加速也就是使用GPU绘制
GPU如何实现硬件加速
原因一:GPU分担一部分cpu工作任务,同步完成一些绘制工作,从而实现了硬件加速
原因二:跟GPU设计有关,它对绘制简单图形有优势。
原因三:跟软件流程有关。
但是硬件加速也存在一些问题,比较常见的就是兼容性问题
在动画中使用setLayerType如下可以提高性能,这个也是google推荐的
属性动画的知识点差不多都涉及到了,还有一个就是文本相关的动画,这里就不写出来,如果大家需要看的话,代码demo已经上传到github。目录在property-animator下。