Android实现3D页面加载进度条动画

一.概述

最近在研究公司的代码,发现每次切换页面时做的进度条效果还挺不错的,所以想深入研究一下,今天就带大家来看看到底是如何实现的,首先上效果图
这里写图片描述

二.实现

上面的进度条最大的特点就是有种立体的感觉,中间的文字貌似是在垂直于平面在转动,下面看看具体的实现:
1.首先我们需要四张图片

SOUFUN_LOADING_BAR

SOUFUN_LOADING
这里写图片描述

SOUFUN_LOADING1
这里写图片描述

SOUFUN_LOADING2
这里写图片描述

然后我们开始写自定义的View,名字叫做PageLoadingView,那么这个类该继承什么呢?在这里我们需要继承FrameLayout,因为我们要把这些图片添加到当前页面中去的,所有我们必须继承一个具体的ViewGroup的实现类,这里我们选择最简单的FrameLayout。

public class PageLoadingView extends FrameLayout{
    //背景图
    protected static int SOUFUN_LOADING_BAR = R.drawable.page_loading_bar;
    //文字"天"的图片
    protected static int SOUFUN_LOADING = R.drawable.page_loading;
    //文字"下"的图片
    protected static int SOUFUN_LOADING1 = R.drawable.page_loading1;
    //文字"贷"的图片
    protected static int SOUFUN_LOADING2 = R.drawable.page_loading2;

然后我们做初始化动作,并且在构造函数中调用


    /**
     * 在布局文件中使用对象是会调用此方法
     * @param context
     * @param attrs
     */
    public PageLoadingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    /**
     * 在代码中创建对象是会调用此方法
     * @param context
     */
    public PageLoadingView(Context context) {
        super(context);
        init(context);
    }

    /**
     * 初始化,把图片添加到布局中
     * @param context
     */
    public void init(Context context) {
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        //创建一个ImageView对象
        iv_loading_bar = new ImageView(context);
        //设置ImageView所显示的图片
        iv_loading_bar.setImageResource(SOUFUN_LOADING_BAR);
        iv_loading_bar.setLayoutParams(params);
        iv_loading = new ImageView(context);
        iv_loading.setImageResource(SOUFUN_LOADING2);
        iv_loading.setLayoutParams(params);
        //将背景图片添加到FrameLayout
        this.addView(iv_loading_bar);
        //把文字图片添加到FrameLayout中,这里可以随意添加一张,因为我们会在动画开始的时候动态替换
        this.addView(iv_loading);
    }

我们可以看到外面的圆圈很明显是个旋转动画,我们看看怎么实现:

private Animation createRotateAnimation() {
        //绕自身中心旋转,从0到360度
        Animation animation = new RotateAnimation(0, +360,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                0.5f);
        //动画自执行时长
        animation.setDuration(900);
        //无限旋转
        animation.setRepeatCount(Animation.INFINITE);
        //设置线性插值器,意思就是让匀速旋转
        animation.setInterpolator(new LinearInterpolator());
        return animation;
    }

接下来该看看重点了,也就是中间文字的旋转动画,我们先上代码,然后解释


    private Rotate3d createRotate3dAnimation() {
        //获取旋转点所在的X坐标,这里为容器的中心getWidth获得的就是整个容器的宽度
        final float centerX = this.getWidth() / 2.0f;
        final Rotate3d rotation = new Rotate3d(-90, 90, centerX, 0, 0, true);
        rotation.setDuration(500);
        rotation.setRepeatCount(Animation.INFINITE);
        rotation.setInterpolator(new LinearInterpolator());
        rotation.setAnimationListener(mAnimationListener);
        return rotation;
    }

    private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() {
        int time = 1;

        /**
         * 动画开始时执行此方法
         * @param animation
         */
        @Override
        public void onAnimationStart(Animation animation) {
            //将中心的文字设置为"天"
            iv_loading.setImageResource(SOUFUN_LOADING);
        }

        /**
         * 动画不断重复时执行此方法,因为我们把动画的模式都设置为了INFINITE,也就是无限循环
         * 所以此方法会不断的执行
         * @param animation
         */
        @Override
        public void onAnimationRepeat(Animation animation) {
            //清除缓存
            iv_loading.destroyDrawingCache();
            if (time % 3 == 0) {
                iv_loading.setImageResource(SOUFUN_LOADING);
            } else if (time % 3 == 1) {
                iv_loading.setImageResource(SOUFUN_LOADING1);
            }else{
                iv_loading.setImageResource(SOUFUN_LOADING2);
            }
            time++;

        }

        @Override
        public void onAnimationEnd(Animation animation) { // 动画结束时执行此方法

        }
    };

我们先来看比较简单的,也就是图片是如何不断变化的,重点就在onAnimationRepeat方法里面,在这个方法里面我们定义了一个int类型的time,这里我们根据time值的不同,每次就会变化不同的图片,因为onAnimationRepeat方法是不断执行的,time的值是不断增加的,所以每隔固定的时间我们就可以看到进度条中间的文字图片在改变。

然后我们看看比较重要的一行

 final Rotate3d rotation = new Rotate3d(-90, 90, centerX, 0, 0, true);

这里有一个类Rotate3d,重点在这里,我们进去看看,先给出完整代码:

public class Rotate3d extends Animation {
    private final float mFromDegrees;
    private final float mToDegrees;
    private final float mCenterX;
    private final float mCenterY;
    private final float mDepthZ;
    private final boolean mReverse;
    private Camera mCamera;

    public Rotate3d(float fromDegrees, float toDegrees, float centerX, float centerY, float depthZ, boolean reverse) {
        mFromDegrees = fromDegrees;
        mToDegrees = toDegrees;
        mCenterX = centerX;
        mCenterY = centerY;
        mDepthZ = depthZ;
        mReverse = reverse;
    }

    /**
     * 将初始化动画组件及其父容器的宽高;通常亦可进行另外的初始化工作
     * @param width
     * @param height
     * @param parentWidth
     * @param parentHeight
     */
    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        mCamera = new Camera();
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        final float fromDegrees = mFromDegrees;
        float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
        final float centerX = mCenterX;
        final float centerY = mCenterY;
        final Camera camera = mCamera;
        //获得代表变换的矩阵对象
        final Matrix matrix = t.getMatrix();
        //保存Camera的状态
        camera.save();
        if (mReverse) {
            //正向旋转,距离Z轴的距离从小到大
            camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
        } else {
            //逆向旋转,距离Z轴的距离从小到大
            camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
        }
        //根据Y轴旋转
        camera.rotateY(degrees);
        //计算当前变换所对应的矩阵并且将其复制给应用此Matrix的对象
        camera.getMatrix(matrix);
        //恢复保存的状态
        camera.restore();
        //根据给定的转换设定matrix
        matrix.preTranslate(-centerX, -centerY);
        matrix.postTranslate(centerX, centerY);
    }
}

我们看到Rotate3d 这个类继承了Animation,这里就是自定义动画。
比较重要的是两个方法initialize和applyTransformation,我们先来看initialize这个方法,

  public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
    }

根据官方的介绍,这个方法的作用是初始化动画组件及其父容器的宽高;通常亦可进行另外的初始化工作,我们在这里创建了一个Camera对象,供后面的使用。

然后我们来看applyTransformation这个方法

  @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);
    }

该方法有两个参数。第一个为动画的进度时间值,范围是[0.0f,1.0f],第二个参数Transformation记录着动画某一帧中变形的原始数据。该方法在动画的每一帧显示过程中都会被调用。
然后我们在这里做了一个线性算法来生成中间角度:

float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);

因为interpolatedTime是不断变化的,所以degrees这个值也是不断变化的。Camera 类是用来实现绕 Y 轴旋转后透视投影的。我们只需要其返回的 Matrix 值 , 这个值会赋给 Transformation 中的矩阵成员,这里涉及到了动画的实现原理,这里就不详细介绍了,大家下来自己研究研究,最后给出PageLoadingView的全部代码:

public class PageLoadingView extends FrameLayout{
    //背景图
    protected static int SOUFUN_LOADING_BAR = R.drawable.page_loading_bar;
    //文字"天"的图片
    protected static int SOUFUN_LOADING = R.drawable.page_loading;
    //文字"下"的图片
    protected static int SOUFUN_LOADING1 = R.drawable.page_loading1;
    //文字"贷"的图片
    protected static int SOUFUN_LOADING2 = R.drawable.page_loading2;

    private static Animation animation;
    private static Rotate3d rotate3d;

    private ImageView iv_loading_bar;
    private ImageView iv_loading;

    /**
     * 在布局文件中使用对象是会调用此方法
     * @param context
     * @param attrs
     */
    public PageLoadingView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    /**
     * 在代码中创建对象是会调用此方法
     * @param context
     */
    public PageLoadingView(Context context) {
        super(context);
        init(context);
    }

    /**
     * 初始化,把图片添加到布局中
     * @param context
     */
    public void init(Context context) {
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        //创建一个ImageView对象
        iv_loading_bar = new ImageView(context);
        //设置ImageView所显示的图片
        iv_loading_bar.setImageResource(SOUFUN_LOADING_BAR);
        iv_loading_bar.setLayoutParams(params);
        iv_loading = new ImageView(context);
        iv_loading.setImageResource(SOUFUN_LOADING2);
        iv_loading.setLayoutParams(params);
        //将背景图片添加到FrameLayout
        this.addView(iv_loading_bar);
        //把文字图片添加到FrameLayout中,这里可以随意添加一张,因为我们会在动画开始的时候替换为第一张
        //也就是"天"这种图片
        this.addView(iv_loading);
    }

    @Override
    public void setVisibility(int visibility) {
        super.setVisibility(visibility);
        if (visibility == View.VISIBLE) {
            animation = createRotateAnimation();
            iv_loading_bar.startAnimation(animation);
            post(new Runnable() {
                @Override
                public void run() {
                    rotate3d = createRotate3dAnimation();
                    iv_loading.startAnimation(rotate3d);
                }
            });
        }
    }

    private Animation createRotateAnimation() {
        //绕自身中心旋转,从0到360度
        Animation animation = new RotateAnimation(0, +360,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
                0.5f);
        //动画自执行时长
        animation.setDuration(900);
        //无限旋转
        animation.setRepeatCount(Animation.INFINITE);
        //设置线性插值器,意思就是让匀速旋转
        animation.setInterpolator(new LinearInterpolator());
        return animation;
    }

    private Rotate3d createRotate3dAnimation() {
        //获取旋转点所在的X坐标,这里为容器的中心getWidth获得的就是整个容器的宽度
        final float centerX = this.getWidth() / 2.0f;
        final Rotate3d rotation = new Rotate3d(-90, 90, centerX, 0, 0, true);
        rotation.setDuration(500);
        rotation.setRepeatCount(Animation.INFINITE);
        rotation.setInterpolator(new LinearInterpolator());
        rotation.setAnimationListener(mAnimationListener);
        return rotation;
    }

    private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() {
        int time = 1;

        /**
         * 动画开始时执行此方法
         * @param animation
         */
        @Override
        public void onAnimationStart(Animation animation) {
            //将中心的文字设置为"天"
            iv_loading.setImageResource(SOUFUN_LOADING);
        }

        /**
         * 动画不断重复时执行此方法,因为我们把动画的模式都设置为了INFINITE,也就是无限循环
         * 所以此方法会不断的执行
         * @param animation
         */
        @Override
        public void onAnimationRepeat(Animation animation) {
            //清除缓存
            iv_loading.destroyDrawingCache();
            if (time % 3 == 0) {
                iv_loading.setImageResource(SOUFUN_LOADING);
            } else if (time % 3 == 1) {
                iv_loading.setImageResource(SOUFUN_LOADING1);
            }else{
                iv_loading.setImageResource(SOUFUN_LOADING2);
            }
            time++;

        }

        @Override
        public void onAnimationEnd(Animation animation) { // 动画结束时执行此方法

        }
    };
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值