58同城加载动画的实现( Android属性动画)

最近看了58同城新版 app ,里面还是做了很多动画特效,其中我看到加载数据时的一个加载动画比较好玩,就试着做了一下,先一起来看看效果
技术分享
很多人看了这个效果图,第一个疑问就是底下的阴影部分是如何实现的?其实如果真要自己动手实现的话,这个问题反而不是问题,而真正有困难的是,如何控制这个图片上升的时候速度减慢,而下降的时候速度加快,当然这个问题只有在动手做的过程中才会发现。
这里还是按步骤来实现
1、实现整个 LoadingView的布局

public class LoadingLayout extends RelativeLayout {
    public LoadingLayout(Context context) {
        this(context, null);
    }

    public LoadingLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoadingLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //添加所需要的效果图片,并根据需求排列
        initView(getContext());
    }
}
    private void initView(Context context) {
        /*固定这几个图片的大小为28个 dp 值*/
        int viewSize = (int) (28 * getResources().getDisplayMetrics().density + .5f);
        /*创建一个 显示圆形图片的View*/
        mCircleView = new View(context);
        /*设置参数*/
        RelativeLayout.LayoutParams circleParams = new LayoutParams(viewSize, viewSize);
        /*让他水平居中显示*/
        circleParams.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
        mCircleView.setLayoutParams(circleParams);
        /*设置背景图片*/
        mCircleView.setBackgroundResource(R.mipmap.loading_yuan);
        /*设置 id,这里的作用,是为了下面阴影的排列,需要用此View 作为参考对象*/
        mCircleView.setId(R.id.action_bar_root);

        /*创建一个显示正方形图片的View*/
        mRectView = new View(context);
        RelativeLayout.LayoutParams rectParams = new LayoutParams(viewSize, viewSize);
        rectParams.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
        mRectView.setLayoutParams(rectParams);
        mRectView.setBackgroundResource(R.mipmap.loading_fangxing);

        /*创建一个显示三角形图片的View*/
        mTriangleView = new View(context);
        RelativeLayout.LayoutParams triangleParams = new LayoutParams(viewSize, viewSize);
        triangleParams.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
        mTriangleView.setLayoutParams(triangleParams);
        mTriangleView.setBackgroundResource(R.mipmap.loading_sanjiao);

        /*创建一个显示底部阴影图片的ImageView*/
        mBottomView = new ImageView(context);
        RelativeLayout.LayoutParams bottomParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        /*设置水平居中*/
        bottomParams.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
        /*设置在圆形图片的下方*/
        bottomParams.addRule(RelativeLayout.BELOW, R.id.action_bar_root);
        mBottomView.setLayoutParams(bottomParams);
        mBottomView.setBackgroundResource(R.mipmap.loading_bottom);
        /*整个Layout 中的View 居中显示*/
        setGravity(Gravity.CENTER);
        /*添加View*/
        addView(mCircleView);
        addView(mRectView);
        addView(mTriangleView);
        addView(mBottomView);

        mRectView.setVisibility(INVISIBLE);
        mTriangleView.setVisibility(INVISIBLE);
    }

到这里,第一步算是完成了,能够正常显示我们想要的数据,接下来看第二步,

2、为这几个 View 设置动画,让他们都动起来,这里说一下底部阴影的实现原理,其实没什么特别的,跟其他三个图片一样,通过属性动画改变其 X的放缩。

    private void startAnim() {
        Log.v("zgy","=========startAnim========") ;
        isAnim = true ;
        if (mCircleView.getVisibility() != VISIBLE){
            mCircleView.setVisibility(VISIBLE);
            mRectView.setVisibility(INVISIBLE);
            mTriangleView.setVisibility(INVISIBLE);
        }
        /*圆形图片的动画集合*/
        mCircleAnim = new AnimatorSet();
        /*设置执行时长800ms*/
        mCircleAnim.setDuration(800L);
        /*这里设置播放动画的个数,移动动画和底部阴影放缩动画*/
        mCircleAnim.playTogether(translationAnim(mCircleView),bottomAnim());
        /*开始动画*/
        mCircleAnim.start();
        /*设置动画监听事件*/
        mCircleAnim.addListener(mCircleListener);

        mRectAnim = new AnimatorSet();
        mRectAnim.setStartDelay(800L);
        mRectAnim.setDuration(800L);
        mRectAnim.playTogether(translationAnim(mRectView),bottomAnim(),rotationAnim(mRectView));
        mRectAnim.start();
        mRectAnim.addListener(mRectListener);

        mTriangleAnim = new AnimatorSet();
        mTriangleAnim.setStartDelay(1600L);
        mTriangleAnim.setDuration(800L);
        mTriangleAnim.playTogether(translationAnim(mTriangleView),bottomAnim(),rotationAnim(mTriangleView));
        mTriangleAnim.start();
        mTriangleAnim.addListener(mTriangleListener);

到这里,动画效果就已经实现了,来看看
技术分享
但是总感觉效果不对劲,一般东西往下掉,给人的感觉是,也往下速度越快,而这里只是做匀速变化,那好,我们来改变插值器,因为这里动画是从下往上再往下算是一个完整的动画,其值为:

        mAnimTransValueRec = new float[7];
        mAnimTransValueRec[0] = 0f;
        mAnimTransValueRec[1] = -50f;
        mAnimTransValueRec[2] = -100f;
        mAnimTransValueRec[3] = -150f;
        mAnimTransValueRec[4] = -100f;
        mAnimTransValueRec[5] = -50f;
        mAnimTransValueRec[6] = 0f;

所以我们设插值器的时候,希望设置先减速再加速的插值器,可是,不巧的是,加速插值器有,减速插值器有,先加速再减速插值器也有,就是没有提供先减速再加速的插值器,不过没关系,我们自己实现一个;
要实现插值器,其实就是把

public interface TimeInterpolator {

    /**
     * Maps a value representing the elapsed fraction of an animation to a value that represents
     * the interpolated fraction. This interpolated value is then multiplied by the change in
     * value of an animation to derive the animated value at the current elapsed animation time.
     *
     * @param input A value between 0 and 1.0 indicating our current point
     *        in the animation where 0 represents the start and 1.0 represents
     *        the end
     * @return The interpolation value. This value can be more than 1.0 for
     *         interpolators which overshoot their targets, or less than 0 for
     *         interpolators that undershoot their targets.
     */
    float getInterpolation(float input);
}

中的 input 值转化成另一个 float,

如果什么都不做处理,就是线性变化,也就是匀速变化

/**
 * An interpolator where the rate of change is constant
 *
 */
public class LinearInterpolator implements Interpolator {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }
}

技术分享

再来看看先加速后减速的插值器

/**
 * An interpolator where the rate of change starts and ends slowly but
 * accelerates through the middle.
 * 
 */
public class AccelerateDecelerateInterpolator implements Interpolator {
    public AccelerateDecelerateInterpolator() {
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
    }
}

技术分享

那怎么来实现先减速后加速的插值器呢,根据上面的图形我们可以知道,要实现我们想要的效果,则在0~0.5之间的曲线应该是凸起来的,而在0.5~1之间应该是凹下去的,跟上图刚好要相反,这样的曲线怎么实现呢,来看一张图
技术分享
以上图片是由两个抛物线组成,如果在0~0.5范围内取抛物线 b,在0.5~1.0取抛物线 a,刚好就是我们想要的曲线。而根据高中所学的数学知识,二次曲线求导可知,我们所截取的曲线其速度就是先减速再加速。
所以实现先减速再加速的插值器代码如下:

public final class DecelerateAccelerateInterpolator implements Interpolator {

    public final float getInterpolation(float input) {

        if (input < 0.5) {
            return - (input * (input/2.0f) -  input/2.0f);
        }else {
            return 1.0F + (input * (2.0F * input) - 2.0F * input) ;
        }
    }
}

ok,记得为动画设置插值器

animator.setInterpolator(new DecelerateAccelerateInterpolator());

最后测试效果就是第一副动态图的效果,明显比第二张动态图跟符合视觉逻辑;

点击源码下载

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。