android自定义加载动画

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

先说下实现思路,上面不断变化形状的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设置需要绘制的图形状态。

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

源码下载

你可能感兴趣的:(加载,android,动画)