android自定义加载动画

本文介绍了一种基于属性动画的自定义LoadingView实现方法,通过不断改变形状展示动态效果,并结合父布局动画实现上下弹动。

今天研究了一下android-shapeLoadingView,的源码,发现其关于view的绘制实现起来比较复杂,就自己写了一个简化版,主要用到的是属性动画,先看效果:
这里写图片描述

先说下实现思路,上面不断变化形状的view是一个自定义的LoadingView每次不断变换重新绘制新的形状,整个效果是一个自定义的layout,加载了一个布局,在该布局当中引入了LoadingView,底部的阴影是一个image,通过shape绘制一个椭圆,整个效果通过属性动画不断改变view的大小和位置。下面看代码:

自定义一个loadingView

这里自定义的loadingView主要是根据当前图形的状态不断绘制新的图形。仅此而已,对于当前loadingView位置的改变则交给父布局使用属性动画来维护。

loadingView包含的属性

private Paint mPaint;
private Shape mCurrentShape = Shape.RECT; //第一次绘制矩形
private Path mPath;

//定义一个枚举,用来标识当前需要绘制的形状类型
public enum Shape{
        CIRCLE,
        RECT,
        RACTANGLE
}

由于LoadingView的功能比较简单,即根据当前图形的状态,绘制新的图形,所以属性也是比较少的。可以看到,这里我主要使用一个Shape枚举类型定义三种图形状态,并将初始值设置为矩形。

在构造函数中做一些初始化工作

public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        //一些初始化工作
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPath = new Path();
    }

重写onDraw方法绘制图形

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //使用switch来判断当前需要绘制图形的形状
        switch (mCurrentShape) {
        case RECT:
            mPaint.setColor(Color.parseColor("#FF9999"));
            canvas.drawRect(0,0,getWidth(),getHeight(), mPaint);
            break;
        case CIRCLE:
            int circleRadius = Math.min(getWidth(), getHeight()) / 2;
            mPaint.setColor(Color.parseColor("#99FFCC"));
            canvas.drawCircle(getWidth() / 2, getHeight() / 2 ,circleRadius, mPaint);
            break;
        case RACTANGLE:
            mPaint.setColor(Color.parseColor("#99CCFF"));
            mPath.reset();
            mPath.moveTo(getWidth() / 2,0);
            mPath.lineTo(0,getHeight());
            mPath.lineTo(getWidth(), getHeight());
            mPath.close();
            canvas.drawPath(mPath, mPaint);
            break;
        default:
            break;
        }
    }

这里我根据当前view需要的图形状态来绘制不同的形状,比较简单。

对外提供接口

/**
     * 改变当前需要绘制的形状,该接口是提供给外边调用的
     * @param currentShape 当前已经绘制的形状
     */
    public void changeShape(Shape currentShape) {
        if (currentShape == Shape.RECT) {
            mCurrentShape = Shape.CIRCLE;
        } else  if (currentShape == Shape.CIRCLE) {
            mCurrentShape = Shape.RACTANGLE;
        } else  if (currentShape == Shape.RACTANGLE) {
            mCurrentShape = Shape.RECT;
        }
        //更改完成图形的形状之后,记得重绘
        invalidate();
    }

    /**
     * 获得当前绘制的形状
     * @return
     */
    public Shape getCurrentShape() {
        return mCurrentShape;
    }

这两个接口是为当前LoadingView的父布局提供的,getCurrentShape可以获取当前的图形状态,changeShape用来重新设置下一个图形状态。

新建loading.xml布局

这里我新建一个布局,等下会在自定义的layout中加载。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:minWidth="260dp"
    android:orientation="vertical" >

    <ImageView
        android:id="@+id/bottom_shadow"
        android:layout_width="23dp"
        android:layout_height="5dp"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="82dp"
        android:src="@drawable/show" />

    <TextView
        android:id="@+id/bottom_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/bottom_shadow"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="18dp"
        android:minWidth="44dp"
        android:text="loading...."
        android:textColor="#757575"
        android:textSize="14sp" />

    <com.example.selfloading.LoadingView
        android:id="@+id/shapeLoadingView"
        android:layout_width="18dp"
        android:layout_height="18dp"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

这里将之前自定义的LoadingView放入到该布局中的顶部,bottom_shadow是一个Image类型,drawble/show如下:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"

    android:shape="oval"
    >
    <solid android:color="@color/shadow"/>

</shape>

可以看到就是一个椭圆。

创建ContainLoadViewLayout加载布局

ContainLoadViewLayout主要是利用属性动画,实现整个效果的,LoadingView只是用来绘制不同的图形而已。

重写onFinishInflate

@Override
protected void onFinishInflate() {
        super.onFinishInflate();

        View view = LayoutInflater.from(getContext()).inflate(R.layout.loading,null);
        mLoadingView = (LoadingView) view.findViewById(R.id.shapeLoadingView); //当前正在绘制的图形
        mOval = (ImageView) view.findViewById(R.id.bottom_shadow); //用来显示底部的阴影
        addView(view);

        mFallDistance = (int) TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP, 70, getContext().getResources()
                        .getDisplayMetrics());

        //加载完成布局结束以后启动下落的动画
        startFall();
    }

这里首先获得需要改变属性值的两个view,一个是LoadingView,一个是底部的阴影(ImageView), 然后将整个布局加入到该Layout当中。
mFallDistance 表示下落的距离。在当前布局加载完成之后,开始下落的属性动画,利用利用AnimatorListenerAdapter监听下落的属性动画完成之后,开始上升的属性动画。

动画的实现

/**
     * 下落的动画
     */
    public void startFall() {
        ObjectAnimator rectAnimator = ObjectAnimator.ofFloat(mLoadingView, "translationY", mFallDistance,0);
        ObjectAnimator ovalAnimator = ObjectAnimator.ofFloat(mOval, "scaleX", 1.0f,0.3f);
        rectAnimator.setInterpolator(new DecelerateInterpolator());
        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(1000);
        animatorSet.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                startUp();
            }
        });

        animatorSet.play(rectAnimator).with(ovalAnimator);
        animatorSet.start();
    }

/**
 * 弹跳起来的动画
 */
 public void startUp() {
    ObjectAnimator rectUpAnimator = ObjectAnimator.ofFloat(mLoadingView, "translationY", 0,mFallDistance);
    ObjectAnimator rectRotateAnimator = ObjectAnimator.ofFloat(mLoadingView, "rotation", 0,180);
    if (mLoadingView.getCurrentShape() == Shape.RACTANGLE) {//如果是三角形,反向旋转
            rectRotateAnimator = ObjectAnimator.ofFloat(mLoadingView, "rotation", 0,-180);
    }
    ObjectAnimator ovalAnimator = ObjectAnimator.ofFloat(mOval, "scaleX", 0.3f,1.0f);
    rectUpAnimator.setInterpolator(new AccelerateInterpolator());
    AnimatorSet animatorSet = new AnimatorSet();
    animatorSet.setDuration(1000);
    animatorSet.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
                //在弹跳动画完成之后,调用changeShape,该方法会重新设置当前需要绘制的图形,并重绘该图形
                   mLoadingView.changeShape(mLoadingView.getCurrentShape());
                //交错执行上升和下落的动画
                startFall();
            }
        });

        animatorSet.play(rectUpAnimator).with(rectRotateAnimator).with(ovalAnimator);
        animatorSet.start();
}

可以看到这里我使用AnimatorListenerAdapter监听动画结束,然后交替执行下落和上升的动画。在下落完成的时候,重新为LoadingView设置需要绘制的图形状态。

整个代码实现起来还是比较简单的,今天就到这里了,希望大家喜欢。

源码下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值