Android 图片弹跳动画

这几天看到一个小动画,觉得有点意思,就自己实现来看看,先看效果图


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




  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值