title: Android动画学习分析与实践(1)
date: 2016-11-02 10:36:01
tags:
前言
本文首发于本人博客--精分道长的博客
Android进阶必须学习的就是动画和自定义View(ViewGroup)了。在学习完基本概念之后,仍然发现自己不能很好的写出动画。
我觉的分析和模仿别人的动画是一个很好的思路。
关于基础可以看教程:
Android 动画总结
以及属性动画:
Android 属性动画(Property Animation) 完全解析 (上)
Android 属性动画(Property Animation) 完全解析 (下)
Android 自定义View (一)
Android 自定义View (二) 进阶
Android 自定义View (三) 圆环交替 等待效果
Android 自定义View (四) 视频音量调控
实例分析1---直播点赞动画
参考资料android 飘心动画(直播点赞)效果
效果图
代码分析
FlowLikeView.java
首先重写一个GroupView继承RelativeLayout 以及变量添加
public class FlowLikeView extends RelativeLayout {
private List mLikeDrawables; // 图片的集合
private LayoutParams mLayoutParams; // 用于设置动画对象的位置参数
private Random mRandom; // 用于产生随机数,如生成随机图片
private int mViewWidth; // 控件的宽度
private int mViewHeight; // 控件的高度
private int mPicWidth; // 图片的宽度
private int mPicHeight; // 图片的高度
private int mChildViewHeight; // 在 XML 布局文件中添加的子View的总高度
三个构造函数:在第三个初始化数据
public FlowLikeView(Context context) {
this(context, null);
}
public FlowLikeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FlowLikeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initParams();
}
private void initParams() {
mLikeDrawables = new ArrayList<>();
mLikeDrawables.add(generateDrawable(R.drawable.heart0));
mLikeDrawables.add(generateDrawable(R.drawable.heart1));
mLikeDrawables.add(generateDrawable(R.drawable.heart2));
mLikeDrawables.add(generateDrawable(R.drawable.heart3));
mLikeDrawables.add(generateDrawable(R.drawable.heart4));
mLikeDrawables.add(generateDrawable(R.drawable.heart5));
mLikeDrawables.add(generateDrawable(R.drawable.heart6));
mLikeDrawables.add(generateDrawable(R.drawable.heart7));
mLikeDrawables.add(generateDrawable(R.drawable.heart8));
// 获取图片的宽高, 由于图片大小一致,故直接获取第一张图片的宽高
mPicWidth = mLikeDrawables.get(0).getIntrinsicWidth();
mPicHeight = mLikeDrawables.get(0).getIntrinsicHeight();
// 初始化布局参数
mLayoutParams = new LayoutParams(mPicWidth, mPicHeight);
mLayoutParams.addRule(CENTER_HORIZONTAL);
mLayoutParams.addRule(ALIGN_PARENT_BOTTOM);
//随机数
mRandom = new Random();
}
private Drawable generateDrawable(int resID) {
return ContextCompat.getDrawable(getContext(), resID);
}
重写onMeasure使显示之前进行测量,主要是为了让动画显示在其他View上面
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mChildViewHeight <= 0) {
for (int i = 0, size = getChildCount(); i < size; i++) {
View childView = getChildAt(i);
measureChild(childView, widthMeasureSpec, heightMeasureSpec);
mChildViewHeight += childView.getMeasuredHeight();
}
// 设置底部间距
mLayoutParams.bottomMargin = mChildViewHeight;
}
}
重写onSizeChange函数,为了能够及时获取View的高度
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = getWidth();
mViewHeight = getHeight();
}
动态添加ImageView(设置为TextView的点击事件)并设置动画
public void addLikeView() {
ImageView likeView = new ImageView(getContext());
likeView.setImageDrawable(mLikeDrawables.get(mRandom.nextInt(mLikeDrawables.size())));
likeView.setLayoutParams(mLayoutParams);
addView(likeView);
startAnimation(likeView);
}
动画分为两部分:
第一部分出场:
private AnimatorSet generateEnterAnimation(View target) {
ObjectAnimator alpha = ObjectAnimator.ofFloat(target, "alpha", 0.2f, 1f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, "scaleX", 0.5f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, "scaleY", 0.5f, 1f);
AnimatorSet enterAnimation = new AnimatorSet();
enterAnimation.playTogether(alpha, scaleX, scaleY);
enterAnimation.setDuration(150);
enterAnimation.setTarget(target);
return enterAnimation;
}
第二部分:贝塞尔曲线运动动画--属性动画的ValueObjec
/**
* 生成曲线运动动画
*
* @return 动画集合
*/
private ValueAnimator generateCurveAnimation(View target) {
CurveEvaluator evaluator = new CurveEvaluator(generateCTRLPointF(1), generateCTRLPointF(2));
ValueAnimator valueAnimator = ValueAnimator.ofObject(evaluator,
new PointF((mViewWidth - mPicWidth) / 2, mViewHeight - mChildViewHeight - mPicHeight),
new PointF((mViewWidth) / 2 + (mRandom.nextBoolean() ? 1 : -1) * mRandom.nextInt(100), 0));
valueAnimator.setDuration(3000);
valueAnimator.addUpdateListener(new CurveUpdateLister(target));
valueAnimator.setTarget(target);
return valueAnimator;
}
/**
* 生成贝塞儿曲线的控制点
*
* @param value 设置控制点 y 轴上取值区域
* @return 控制点的 x y 坐标
*/
private PointF generateCTRLPointF(int value) {
PointF pointF = new PointF();
pointF.x = mViewWidth / 2 - mRandom.nextInt(100);
pointF.y = mRandom.nextInt(mViewHeight / value);
return pointF;
}
/**
* 自定义估值算法, 计算对象当前运动的具体位置 Point
*/
private class CurveEvaluator implements TypeEvaluator {
// 由于这里使用的是三阶的贝塞儿曲线, 所以我们要定义两个控制点
private PointF ctrlPointF1;
private PointF ctrlPointF2;
public CurveEvaluator(PointF ctrlPointF1, PointF ctrlPointF2) {
this.ctrlPointF1 = ctrlPointF1;
this.ctrlPointF2 = ctrlPointF2;
}
@Override
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
// 这里运用了三阶贝塞儿曲线的公式, 请自行上网查阅
float leftTime = 1.0f - fraction;
PointF resultPointF = new PointF();
// 三阶贝塞儿曲线
resultPointF.x = (float) Math.pow(leftTime, 3) * startValue.x
+ 3 * (float) Math.pow(leftTime, 2) * fraction * ctrlPointF1.x
+ 3 * leftTime * (float) Math.pow(fraction, 2) * ctrlPointF2.x
+ (float) Math.pow(fraction, 3) * endValue.x;
resultPointF.y = (float) Math.pow(leftTime, 3) * startValue.y
+ 3 * (float) Math.pow(leftTime, 2) * fraction * ctrlPointF1.y
+ 3 * leftTime * fraction * fraction * ctrlPointF2.y
+ (float) Math.pow(fraction, 3) * endValue.y;
return resultPointF;
}
}
/**
* 动画曲线路径更新监听器, 用于动态更新动画作用对象的位置
*/
private class CurveUpdateLister implements ValueAnimator.AnimatorUpdateListener {
private View target;
public CurveUpdateLister(View target) {
this.target = target;
}
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 获取当前动画运行的状态值, 使得动画作用对象沿着曲线(涉及贝塞儿曲线)运动
PointF pointF = (PointF) animation.getAnimatedValue();
ViewCompat.setX(target, pointF.x);
ViewCompat.setY(target, pointF.y);
// 改变对象的透明度
ViewCompat.setAlpha(target, 1 - animation.getAnimatedFraction());
}
}
重写回收监听类:
/**
* 动画结束监听器,用于释放无用的资源
*/
private class AnimationEndListener extends AnimatorListenerAdapter {
private View target;
public AnimationEndListener(View target) {
this.target = target;
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
removeView(target);
}
}
最后要组合两个动画,为一个,返回给ingview调用:
private void startAnimation(View target) {
// 设置进入动画
AnimatorSet enterAnimator = generateEnterAnimation(target);
// 设置路径动画
ValueAnimator curveAnimator = generateCurveAnimation(target);
// 设置动画集合, 先执行进入动画,最后再执行运动曲线动画
AnimatorSet finalAnimatorSet = new AnimatorSet();
finalAnimatorSet.setTarget(target);
finalAnimatorSet.playSequentially(enterAnimator, curveAnimator);
finalAnimatorSet.addListener(new AnimationEndListener(target));
finalAnimatorSet.start();
}
步骤图解
关键就在两个动画的设置:
这里分别用到了属性动画里的两个类:
ObjectAnimator和ValueAnimator
ObjectAnimator动画
private AnimatorSet generateEnterAnimation(View target) {
ObjectAnimator alpha = ObjectAnimator.ofFloat(target, "alpha", 0.2f, 1f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(target, "scaleX", 0.5f, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(target, "scaleY", 0.5f, 1f);
AnimatorSet enterAnimation = new AnimatorSet();
enterAnimation.playTogether(alpha, scaleX, scaleY);
enterAnimation.setDuration(150);
enterAnimation.setTarget(target);
return enterAnimation;
}
- 传入View
- ObjectAnimator.ofFloat(target, "alpha", 0.2f, 1f);方法实例化一个ObjectAnimator。这几个参数分别为(View,View的某个属性,变化初始值,变化结束值)
- 使用AnimatorSet组合多个ObjectAnimator
- 设置其他属性
这里也可以不设置真正的属性,在监听事件里修改属性
public void rotateyAnimRun(final View view)
{
ObjectAnimator anim = ObjectAnimator//
.ofFloat(view, "zhy", 1.0F, 0.0F)//
.setDuration(500);//
anim.start();
anim.addUpdateListener(new AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
float cVal = (Float) animation.getAnimatedValue();
view.setAlpha(cVal);
view.setScaleX(cVal);
view.setScaleY(cVal);
}
});
}
ValueAnimator动画
看一个简化的版本
public void paowuxian(View view)
{
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setDuration(3000);
valueAnimator.setObjectValues(new PointF(0, 0));
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setEvaluator(new TypeEvaluator()
{
// fraction = t / duration
@Override
public PointF evaluate(float fraction, PointF startValue,
PointF endValue)
{
Log.e(TAG, fraction * 3 + "");
// x方向200px/s ,则y方向0.5 * 10 * t
PointF point = new PointF();
point.x = 200 * fraction * 3;
point.y = 0.5f * 200 * (fraction * 3) * (fraction * 3);
return point;
}
});
valueAnimator.start();
valueAnimator.addUpdateListener(new AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
PointF point = (PointF) animation.getAnimatedValue();
mBlueBall.setX(point.x);
mBlueBall.setY(point.y);
}
});
}
也是只在UpdateListener事件里更新对象的属性。
模仿
雪花飘落
核心代码:
private ValueAnimator generateCurveAnimation(final View target) {
final ValueAnimator valueAnimator = new ValueAnimator();
final float wRadom = mRandom.nextFloat();
valueAnimator.setDuration(3000);
valueAnimator.setObjectValues(new PointF(0, 0));
valueAnimator.setInterpolator(new DecelerateInterpolator());
valueAnimator.setEvaluator(new TypeEvaluator() {
@Override
public PointF evaluate(float fraction, PointF startValue,
PointF endValue) {
PointF point = new PointF();
float WH = mViewWidth - mPicWidth;
point.y = fraction * (mViewHeight - mPicHeight);
point.x = (float) Math.cos(fraction * 2 * Math.PI) * WH / 4 + WH*wRadom;
return point;
}
});
valueAnimator.setTarget(target);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF point = (PointF) animation.getAnimatedValue();
target.setX(point.x);
target.setY(point.y);
}
});
return valueAnimator;
}
效果图
总结
仔细观察,数学要好。