这几天看到一个小动画,觉得有点意思,就自己实现来看看,先看效果图
OK,这个效果基本功能就是,一个图片,从顶部掉下来,完后弹几下,再停止,实现起来还是比较简单的,不过也走了点小弯路,这里记录下。
有段时间做自定义控件比较多,有点中毒了,看到任何效果第一个先想到自定义控件,所以一开始我是用自定义控件嵌套自己用动画计算距离来实现,后来发现没必要,但基本思路是一致的,这里先看看自定义控件嵌套动画如何实现。
首先自定义一个ViewGroup, 看一下里面用到的几个变量
private int mWidth; // 屏幕宽度
private int mHeight; // 屏幕高度
private boolean mFirst = true; // 是否第一次执行
private int mTotalRound; // 总次数
private int mCurRound; // 当前次数
private long mTime; // 动画时间
都有注释,主要注意下次数,其中图片落下或弹起,都算一次动画
完后实现一个加入图片的方法
private void init() {
ImageView view = new ImageView(mContext);
view.setBackgroundResource(R.mipmap.jump);
view.setScaleType(ImageView.ScaleType.FIT_XY);
addView(view);
}
这没什么好说,就是定义一个ImageView,设置图片,全屏,完后将ImageView加入ViewGroup。
接下来,实现onMeasure方法,获取屏幕宽高,这个在后面动画中有用
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = MeasureSpec.getSize(widthMeasureSpec); //获取ViewGroup宽度
mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取ViewGroup高度
setMeasuredDimension(mWidth, mHeight); //设置ViewGroup的宽高
if (mFirst) {
mFirst = false;
beginAnimation();
}
}
这里mFirst是一个布尔变量,初始为true,这里就是动画只执行一次,不管进入onMeasure方法多少次,beginAnimation就是我们要进行的动画。
实现动画前,我们先来仔细观察一下这个动画,这个动画看着挺简单,但是有几个细节还是要注意:
1. 动画弹起的高度越来越小,我这里是第一次弹起屏幕的高度的1/2,第二次弹起1/4,第三次弹起1/8,以此类推
2. 我们将图片的一次落下或弹起看成一次动画,动画的时间越来越短,假设第一次落下动画需要1秒,那第一次弹起就需要1/2秒,第二次落下也是1/2秒,第二次弹起则需要1/4秒,以此类推
3. 下落的时候,速度越来越快,弹起的时候,速度越来越慢
了解了这些细节后,我就可以用layout方法来动态改变ImageView的位置,从而实现动画的效果,layout方法很简单
public void layout(int l, int t, int r, int b)
传入left, top, right, bottom的值来绝对位置,left和right我们不用关注,因为图片是上下移动,所以left恒为0,而right恒为屏幕的宽度,而top和bottom是实时变化的,按顺序看下top和bottom的值变化,假设屏幕高度为mHeight
第一次动画,图片从屏幕的顶部下落到屏幕的底部,top的变化为从-mHeight到0,bottom的变化从0到mHeight
第二次动画,图片从屏幕的底部弹起到图片的底部正好在屏幕高度的1/2处,top的变化从0到-mHeight/2,bottom的变化从mHeight到mHeight/2
第三次动画,图片底部从屏幕高度的1/2处下落到屏幕的底部,top的变化从-mHeight/2到0,bottom的变化从mHeight/2到mHeight
以此类推,后面的动画都是类似的,这些都清楚后,直接来看动画如何实现
private void beginAnimation() {
final int height;
if (mCurRound % 2 == 0) { // 向下
height = (int) (-mHeight * Math.pow(0.5, mCurRound / 2));
} else { // 向上
height = (int) (-mHeight * Math.pow(0.5, (mCurRound + 1) / 2));
}
Animation translateAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (mCurRound % 2 == 0) {
getChildAt(0).layout(0, (int) (height * (1 - interpolatedTime)), mWidth, mHeight + height - (int) (interpolatedTime * height));
} else {
getChildAt(0).layout(0, (int) (height * interpolatedTime), mWidth, mHeight + (int) (height * interpolatedTime));
}
}
};
if (mCurRound % 2 == 0) {
translateAnimation.setInterpolator(new AccelerateInterpolator());
translateAnimation.setDuration((long)(mTime * Math.pow(0.5, mCurRound / 2)));
} else {
translateAnimation.setInterpolator(new DecelerateInterpolator());
translateAnimation.setDuration((long)(mTime * Math.pow(0.5, (mCurRound + 1) / 2)));
}
startAnimation(translateAnimation);
translateAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mCurRound++;
if (mCurRound < mTotalRound) {
beginAnimation();
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
mCurRound就是当前动画的次数,从0开始计数,取模2为0,也就是偶数,也就是下落,取模2为1,也就是奇数,也就是弹起,后面的applyTransformation方法,有一个参数interpolatedTime,在动画执行的过程中,会不断调用applyTransformation这个方法,而interpolatedTime的值从0变化1,我们可以根据这个值,来动态计算当前的高度,而计算方法参考上面的每次动画的top和bottom的值变化范围,自己在纸上画画就知道了。后面的
if (mCurRound % 2 == 0) {
translateAnimation.setInterpolator(new AccelerateInterpolator());
translateAnimation.setDuration((long)(mTime * Math.pow(0.5, mCurRound / 2)));
} else {
translateAnimation.setInterpolator(new DecelerateInterpolator());
translateAnimation.setDuration((long)(mTime * Math.pow(0.5, (mCurRound + 1) / 2)));
}
就是之前分析的,动画下落和弹起的速率变化不一样,动画时间也越来越短。
最后就是在动画执行结束的时候,判断下当前动画执行了多少个了,如果没执行完就继续执行下一个动画,这里是一个递归的调用。代码实现起来还是蛮简单的,关键是前面的分析过程,和细节的注意,下面贴出完整代码
public class JumpActivity extends Activity {
private int totalRound = 10;
private int time = 2000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new BounceView(this, totalRound, time));
}
}
public class BounceView extends ViewGroup {
private Context mContext;
private int mWidth; // 屏幕宽度
private int mHeight; // 屏幕高度
private boolean mFirst = true; // 是否第一次执行
private int mTotalRound; // 总次数
private int mCurRound; // 当前次数
private long mTime; // 动画时间
public BounceView(Context context) {
super(context);
init();
}
public BounceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public BounceView(Context context, int roundNum, long time) {
super(context);
mContext = context;
mTime = time;
mTotalRound = roundNum;
init();
}
private void init() {
ImageView view = new ImageView(mContext);
view.setBackgroundResource(R.mipmap.jump);
view.setScaleType(ImageView.ScaleType.FIT_XY);
addView(view);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mWidth = MeasureSpec.getSize(widthMeasureSpec); //获取ViewGroup宽度
mHeight = MeasureSpec.getSize(heightMeasureSpec); //获取ViewGroup高度
setMeasuredDimension(mWidth, mHeight); //设置ViewGroup的宽高
if (mFirst) {
mFirst = false;
beginAnimation();
}
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
private void beginAnimation() {
final int height;
if (mCurRound % 2 == 0) { // 向下
height = (int) (-mHeight * Math.pow(0.5, mCurRound / 2));
} else { // 向上
height = (int) (-mHeight * Math.pow(0.5, (mCurRound + 1) / 2));
}
Animation translateAnimation = new Animation() {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
if (mCurRound % 2 == 0) {
getChildAt(0).layout(0, (int) (height * (1 - interpolatedTime)), mWidth, mHeight + height - (int) (interpolatedTime * height));
} else {
getChildAt(0).layout(0, (int) (height * interpolatedTime), mWidth, mHeight + (int) (height * interpolatedTime));
}
}
};
if (mCurRound % 2 == 0) {
translateAnimation.setInterpolator(new AccelerateInterpolator());
translateAnimation.setDuration((long)(mTime * Math.pow(0.5, mCurRound / 2)));
} else {
translateAnimation.setInterpolator(new DecelerateInterpolator());
translateAnimation.setDuration((long)(mTime * Math.pow(0.5, (mCurRound + 1) / 2)));
}
startAnimation(translateAnimation);
translateAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
mCurRound++;
if (mCurRound < mTotalRound) {
beginAnimation();
}
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
}
}
这是第一种方法,我们自定义了控件,而且还在applyTransformation中做了计算,后来想了下,其实没必要,不使用自定义控件,不自己去计算,也可以实现,思路其实差不多,唯一的区别就是我们可以使用属性动画,直接用ObjectAnimator的ofFloat方法来移动图片就行了,这里因为思路都是一致的,我就直接贴代码了:
先声明一个布局文件jump_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/move"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/jump"
android:scaleType="fitXY"
/>
</LinearLayout>
完后声明Java类JumpActivity.java:
public class JumpActivity extends Activity {
private ImageView view;
private int mHeight;
private int totalRound = 10;
private int curRound = 0;
private int time = 2000;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(new BounceView(this, totalRound, time));
setContentView(R.layout.jump_layout);
view = (ImageView) findViewById(R.id.move);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
Rect outRect = new Rect();
getWindow().findViewById(Window.ID_ANDROID_CONTENT).getDrawingRect(outRect);
mHeight = outRect.height();
beginTransAnimation();
}
}
private void beginTransAnimation() {
final int height;
if (curRound % 2 == 0) { // 向下
height = (int) (-mHeight * Math.pow(0.5, curRound / 2));
} else { // 向上
height = (int) (-mHeight * Math.pow(0.5, (curRound + 1) / 2));
}
float from = 0;
float to = 0;
if (curRound % 2 == 0) {
from = height;
to = 0;
} else {
from = 0;
to = height;
}
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationY", from, to);
animator.setInterpolator(new AccelerateInterpolator());
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
curRound++;
if (curRound <= totalRound) {
beginTransAnimation();
}
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
if (curRound % 2 == 0) {
animator.setInterpolator(new AccelerateInterpolator());
animator.setDuration((long) (time * Math.pow(0.5, curRound / 2))).start();
} else {
animator.setInterpolator(new DecelerateInterpolator());
animator.setDuration((long) (time * Math.pow(0.5, (curRound + 1) / 2))).start();
}
}
}
PS:第三种实现方式可见Android 图片弹跳动画2