您的位置:首页 > 移动开发 > Android开发

Android加载动画系列——BalloonLoading

2016-06-05 22:56 525 查看
Android加载动画系列——BalloonLoading
       已经好久没有写博客了呢~,下文的源码并非小编原创,而是来自github上的一位大神,小编只是偶然在微博上看到了一篇文章,小编被gif中华丽的效果吸引啦!小编想自己动手敲一敲,做个笔记什么的,也方便以后万一遇到这样的需求就不需要到处去找资源,在此小编奉上原文地址:https://github.com/dinuscxj/LoadingDrawable
       小编本着利人利己的原则来写这篇文章,恳请各位读者老爷不要鄙视小编(可怜状~)。
       让我们先来看看效果图:


 
       小编对Paint的使用不熟,所以只能简单的把源码贴出来供大家阅读。

1、LoadingDrawable.java源码如下:

public class LoadingDrawableextends
Drawable implementsAnimatable {

    private LoadingRenderermLoadingRender;

    private final Callback
mCallback
= newCallback() {

        @Override

        public void invalidateDrawable(Drawabled) {

            invalidateSelf();

        }

        @Override

        public void scheduleDrawable(Drawabled, Runnable what,long
when) {

            scheduleSelf(what, when);

        }

        @Override

        public void unscheduleDrawable(Drawabled, Runnable what) {

            unscheduleSelf(what);

        }

    };

    public LoadingDrawable(LoadingRendererloadingRender) {

        this.mLoadingRender= loadingRender;

        this.mLoadingRender.setCallback(mCallback);

    }

    @Override

    public void draw(Canvascanvas) {

        mLoadingRender.draw(canvas,getBounds());

    }

    @Override

    public void setAlpha(intalpha) {

        mLoadingRender.setAlpha(alpha);

    }

    @Override

    public void setColorFilter(ColorFiltercf) {

        mLoadingRender.setColorFilter(cf);

    }

    @Override

    public int getOpacity(){

        return PixelFormat.TRANSLUCENT;

    }

    @Override

    public void start() {

        mLoadingRender.start();

    }

    @Override

    public void stop() {

        mLoadingRender.stop();

    }

    @Override

    public boolean isRunning(){

        return mLoadingRender.isRunning();

    }

    @Override

    public int getIntrinsicHeight(){

        return (int) (mLoadingRender.getHeight()+1);

    }

    @Override

    public int getIntrinsicWidth(){

        return (int) (mLoadingRender.getWidth()+1);

    }

}
 

2、抽象类LoadingRenderer源码如下:

public abstract class LoadingRenderer {
    private static final long ANIMATION_DURATION = 1333;

    private static final float DEFAULT_SIZE = 56.0f;
    private static final float DEFAULT_CENTER_RADIUS = 12.5f;
    private static final float DEFAULT_STROKE_WIDTH = 2.5f;

    protected float mWidth;
    protected float mHeight;
    protected float mStrokeWidth;
    protected float mCenterRadius;

    private long mDuration;
    private Drawable.Callback mCallback;
    private ValueAnimator mRenderAnimator;

    public LoadingRenderer(Context context) {
        setupDefaultParams(context);
        setupAnimators();
    }

    public abstract void draw(Canvas canvas, Rect bounds);

    public abstract void computeRender(float renderProgress);

    public abstract void setAlpha(int alpha);

    public abstract void setColorFilter(ColorFilter cf);

    public abstract void reset();

    public void start() {
        reset();
        setDuration(mDuration);
        mRenderAnimator.start();
    }

    public void stop() {
        mRenderAnimator.cancel();
    }

    public boolean isRunning() {
        return mRenderAnimator.isRunning();
    }

    public void setCallback(Drawable.Callback callback) {
        this.mCallback = callback;
    }

    protected void invalidateSelf() {
        mCallback.invalidateDrawable(null);
    }

    private void setupDefaultParams(Context context) {
        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        final float screenDensity = metrics.density;

        mWidth = DEFAULT_SIZE * screenDensity;
        mHeight = DEFAULT_SIZE * screenDensity;
        mStrokeWidth = DEFAULT_STROKE_WIDTH * screenDensity;
        mCenterRadius = DEFAULT_CENTER_RADIUS * screenDensity;

        mDuration = ANIMATION_DURATION;
    }

    private void setupAnimators() {
        mRenderAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
        mRenderAnimator.setRepeatCount(Animation.INFINITE);
        mRenderAnimator.setRepeatMode(Animation.RESTART);
        //fuck you! the default interpolator is AccelerateDecelerateInterpolator
        mRenderAnimator.setInterpolator(new LinearInterpolator());
        mRenderAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                computeRender((float) animation.getAnimatedValue());
                invalidateSelf();
            }
        });
    }

    protected void addRenderListener(Animator.AnimatorListener animatorListener) {
        mRenderAnimator.addListener(animatorListener);
    }

    public void setCenterRadius(float centerRadius) {
        mCenterRadius = centerRadius;
    }

    public float getCenterRadius() {
        return mCenterRadius;
    }

    public void setStrokeWidth(float strokeWidth) {
        mStrokeWidth = strokeWidth;
    }

    public float getStrokeWidth() {
        return mStrokeWidth;
    }

    public float getWidth() {
        return mWidth;
    }

    public void setWidth(float width) {
        this.mWidth = width;
    }

    public float getHeight() {
        return mHeight;
    }

    public void setHeight(float height) {
        this.mHeight = height;
    }

    public long getDuration() {
        return mDuration;
    }

    public void setDuration(long duration) {
        this.mDuration = duration;
        mRenderAnimator.setDuration(mDuration);
    }
}

 

3、核心的BalloonLoadingRenderer源码如下:

public class BalloonLoadingRenderer extends LoadingRenderer {
    private static final String PERCENT_SIGN = "%";

    private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator();

    private static final float START_INHALE_DURATION_OFFSET = 0.4f;

    private static final float DEFAULT_WIDTH = 200.0f;
    private static final float DEFAULT_HEIGHT = 150.0f;
    private static final float DEFAULT_STROKE_WIDTH = 2.0f;
    private static final float DEFAULT_GAS_TUBE_WIDTH = 48;
    private static final float DEFAULT_GAS_TUBE_HEIGHT = 20;
    private static final float DEFAULT_CANNULA_WIDTH = 13;
    private static final float DEFAULT_CANNULA_HEIGHT = 37;
    private static final float DEFAULT_CANNULA_OFFSET_Y = 3;
    private static final float DEFAULT_CANNULA_MAX_OFFSET_Y = 15;
    private static final float DEFAULT_PIPE_BODY_WIDTH = 16;
    private static final float DEFAULT_PIPE_BODY_HEIGHT = 36;
    private static final float DEFAULT_BALLOON_WIDTH = 38;
    private static final float DEFAULT_BALLOON_HEIGHT = 48;
    private static final float DEFAULT_RECT_CORNER_RADIUS = 2;

    private static final int DEFAULT_BALLOON_COLOR = Color.parseColor("#ffF3C211");
    private static final int DEFAULT_GAS_TUBE_COLOR = Color.parseColor("#ff174469");
    private static final int DEFAULT_PIPE_BODY_COLOR = Color.parseColor("#aa2369B1");
    private static final int DEFAULT_CANNULA_COLOR = Color.parseColor("#ff174469");

    private static final float DEFAULT_TEXT_SIZE = 7.0f;

    private static final long ANIMATION_DURATION = 3333;

    private final Paint mPaint = new Paint();
    private final RectF mCurrentBounds = new RectF();
    private final RectF mGasTubeBounds = new RectF();
    private final RectF mPipeBodyBounds = new RectF();
    private final RectF mCannulaBounds = new RectF();
    private final RectF mBalloonBounds = new RectF();

    private final Rect mProgressBounds = new Rect();

    private float mTextSize;
    private float mProgress;

    private String mProgressText;

    private float mGasTubeWidth;
    private float mGasTubeHeight;
    private float mCannulaWidth;
    private float mCannulaHeight;
    private float mCannulaMaxOffsetY;
    private float mCannulaOffsetY;
    private float mPipeBodyWidth;
    private float mPipeBodyHeight;
    private float mBalloonWidth;
    private float mBalloonHeight;
    private float mRectCornerRadius;

    private int mBalloonColor;
    private int mGasTubeColor;
    private int mCannulaColor;
    private int mPipeBodyColor;

    public BalloonLoadingRenderer(Context context) {
        super(context);
        init(context);
        setupPaint();
    }

    private void init(Context context) {
        final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
        final float screenDensity = metrics.density;

        mTextSize = DEFAULT_TEXT_SIZE * screenDensity;

        mWidth = DEFAULT_WIDTH * screenDensity;
        mHeight = DEFAULT_HEIGHT * screenDensity;
        mStrokeWidth = DEFAULT_STROKE_WIDTH * screenDensity;

        mGasTubeWidth = DEFAULT_GAS_TUBE_WIDTH * screenDensity;
        mGasTubeHeight = DEFAULT_GAS_TUBE_HEIGHT * screenDensity;
        mCannulaWidth = DEFAULT_CANNULA_WIDTH * screenDensity;
        mCannulaHeight = DEFAULT_CANNULA_HEIGHT * screenDensity;
        mCannulaOffsetY = DEFAULT_CANNULA_OFFSET_Y * screenDensity;
        mCannulaMaxOffsetY = DEFAULT_CANNULA_MAX_OFFSET_Y * screenDensity;
        mPipeBodyWidth = DEFAULT_PIPE_BODY_WIDTH * screenDensity;
        mPipeBodyHeight = DEFAULT_PIPE_BODY_HEIGHT * screenDensity;
        mBalloonWidth = DEFAULT_BALLOON_WIDTH * screenDensity;
        mBalloonHeight = DEFAULT_BALLOON_HEIGHT * screenDensity;
        mRectCornerRadius = DEFAULT_RECT_CORNER_RADIUS * screenDensity;

        mBalloonColor = DEFAULT_BALLOON_COLOR;
        mGasTubeColor = DEFAULT_GAS_TUBE_COLOR;
        mCannulaColor = DEFAULT_CANNULA_COLOR;
        mPipeBodyColor = DEFAULT_PIPE_BODY_COLOR;

        setDuration(ANIMATION_DURATION);
    }

    private void setupPaint() {
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(getStrokeWidth());
    }

    @Override
    public void draw(Canvas canvas, Rect bounds) {
        int saveCount = canvas.save();

        RectF arcBounds = mCurrentBounds;
        arcBounds.set(bounds);

        //draw draw gas tube
        mPaint.setColor(mGasTubeColor);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(getStrokeWidth());
        canvas.drawPath(createGasTubePath(mGasTubeBounds), mPaint);

        //draw balloon
        mPaint.setColor(mBalloonColor);
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawPath(createBalloonPath(mBalloonBounds, mProgress), mPaint);

        //draw progress
        mPaint.setColor(mGasTubeColor);
        mPaint.setTextSize(mTextSize);
        mPaint.setStrokeWidth(getStrokeWidth() / 5.0f);
        canvas.drawText(mProgressText, arcBounds.centerX() - mProgressBounds.width() / 2.0f,
                mGasTubeBounds.centerY() + mProgressBounds.height() / 2.0f, mPaint);

        //draw cannula
        mPaint.setColor(mCannulaColor);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(getStrokeWidth());
        canvas.drawPath(createCannulaHeadPath(mCannulaBounds), mPaint);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawPath(createCannulaBottomPath(mCannulaBounds), mPaint);

        //draw pipe body
        mPaint.setColor(mPipeBodyColor);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawRoundRect(mPipeBodyBounds, mRectCornerRadius, mRectCornerRadius, mPaint);

        canvas.restoreToCount(saveCount);
    }

    @Override
    public void computeRender(float renderProgress) {
        RectF arcBounds = mCurrentBounds;
        //compute gas tube bounds
        mGasTubeBounds.set(arcBounds.centerX() - mGasTubeWidth / 2.0f, arcBounds.centerY(),
                arcBounds.centerX() + mGasTubeWidth / 2.0f, arcBounds.centerY() + mGasTubeHeight);
        //compute pipe body bounds
        mPipeBodyBounds.set(arcBounds.centerX() + mGasTubeWidth / 2.0f - mPipeBodyWidth / 2.0f, arcBounds.centerY() - mPipeBodyHeight,
                arcBounds.centerX() + mGasTubeWidth / 2.0f + mPipeBodyWidth / 2.0f, arcBounds.centerY());
        //compute cannula bounds
        mCannulaBounds.set(arcBounds.centerX() + mGasTubeWidth / 2.0f - mCannulaWidth / 2.0f, arcBounds.centerY() - mCannulaHeight - mCannulaOffsetY,
                arcBounds.centerX() + mGasTubeWidth / 2.0f + mCannulaWidth / 2.0f, arcBounds.centerY() - mCannulaOffsetY);
        //compute balloon bounds
        float insetX = mBalloonWidth * 0.333f * (1 - mProgress);
        float insetY = mBalloonHeight * 0.667f * (1 - mProgress);
        mBalloonBounds.set(arcBounds.centerX() - mGasTubeWidth / 2.0f - mBalloonWidth / 2.0f + insetX, arcBounds.centerY() - mBalloonHeight + insetY,
                arcBounds.centerX() - mGasTubeWidth / 2.0f + mBalloonWidth / 2.0f - insetX, arcBounds.centerY());

        if (renderProgress <= START_INHALE_DURATION_OFFSET) {
            mCannulaBounds.offset(0, -mCannulaMaxOffsetY * renderProgress / START_INHALE_DURATION_OFFSET);

            mProgress = 0.0f;
            mProgressText = 0 + PERCENT_SIGN;

            mPaint.setTextSize(mTextSize);
            mPaint.getTextBounds(mProgressText, 0, mProgressText.length(), mProgressBounds);
        } else {
            float exhaleProgress = ACCELERATE_INTERPOLATOR.getInterpolation(1.0f - (renderProgress - START_INHALE_DURATION_OFFSET) / (1.0f - START_INHALE_DURATION_OFFSET));
            mCannulaBounds.offset(0, -mCannulaMaxOffsetY * exhaleProgress);

            mProgress = 1.0f - exhaleProgress;
            mProgressText = adjustProgress((int) (exhaleProgress * 100.0f)) + PERCENT_SIGN;

            mPaint.setTextSize(mTextSize);
            mPaint.getTextBounds(mProgressText, 0, mProgressText.length(), mProgressBounds);
        }
    }

    private int adjustProgress(int progress) {
        progress = progress / 10 * 10;
        progress = 100 - progress + 10;
        if (progress > 100) {
            progress = 100;
        }

        return progress;
    }

    private Path createGasTubePath(RectF gasTubeRect) {
        Path path = new Path();
        path.moveTo(gasTubeRect.left, gasTubeRect.top);
        path.lineTo(gasTubeRect.left, gasTubeRect.bottom);
        path.lineTo(gasTubeRect.right, gasTubeRect.bottom);
        path.lineTo(gasTubeRect.right, gasTubeRect.top);
        return path;
    }

    private Path createCannulaHeadPath(RectF cannulaRect) {
        Path path = new Path();
        path.moveTo(cannulaRect.left, cannulaRect.top);
        path.lineTo(cannulaRect.right, cannulaRect.top);
        path.moveTo(cannulaRect.centerX(), cannulaRect.top);
        path.lineTo(cannulaRect.centerX(), cannulaRect.bottom - 0.833f * cannulaRect.width());
        return path;
    }

    private Path createCannulaBottomPath(RectF cannulaRect) {
        RectF cannulaHeadRect = new RectF(cannulaRect.left, cannulaRect.bottom - 0.833f * cannulaRect.width(),
                cannulaRect.right, cannulaRect.bottom);

        Path path = new Path();
        path.addRoundRect(cannulaHeadRect, mRectCornerRadius, mRectCornerRadius, Path.Direction.CCW);
        return path;
    }

    /**
     * Coordinates are approximate, you have better cooperate with the designer's design draft
     */
    private Path createBalloonPath(RectF balloonRect, float progress) {

        Path path = new Path();
        path.moveTo(balloonRect.centerX(), balloonRect.bottom);

        float progressWidth = balloonRect.width() * progress;
        float progressHeight = balloonRect.height() * progress;
        //draw left half
        float leftIncrementX1 = progressWidth * -0.48f;
        float leftIncrementY1 = progressHeight * 0.75f;
        float leftIncrementX2 = progressWidth * -0.03f;
        float leftIncrementY2 = progressHeight * -1.6f;
        float leftIncrementX3 = progressWidth * 0.9f;
        float leftIncrementY3 = progressHeight * -1.0f;

        path.cubicTo(balloonRect.left + balloonRect.width() * 0.25f + leftIncrementX1, balloonRect.centerY() - balloonRect.height() * 0.4f + leftIncrementY1,
                balloonRect.left - balloonRect.width() * 0.20f + leftIncrementX2, balloonRect.centerY() + balloonRect.height() * 1.15f + leftIncrementY2,
                balloonRect.left - balloonRect.width() * 0.4f + leftIncrementX3, balloonRect.bottom + leftIncrementY3);

//        the results of the left final transformation
//        path.cubicTo(balloonRect.left - balloonRect.width() * 0.13f, balloonRect.centerY() + balloonRect.height() * 0.35f,
//                balloonRect.left - balloonRect.width() * 0.23f, balloonRect.centerY() - balloonRect.height() * 0.45f,
//                balloonRect.left + balloonRect.width() * 0.5f, balloonRect.bottom - balloonRect.height());

        //draw right half
        float rightIncrementX1 = progressWidth * 1.51f;
        float rightIncrementY1 = progressHeight * -0.05f;
        float rightIncrementX2 = progressWidth * 0.03f;
        float rightIncrementY2 = progressHeight * 0.5f;
        float rightIncrementX3 = 0.0f;
        float rightIncrementY3 = 0.0f;

        path.cubicTo(balloonRect.left - balloonRect.width() * 0.38f + rightIncrementX1, balloonRect.centerY() - balloonRect.height() * 0.4f + rightIncrementY1,
                balloonRect.left + balloonRect.width() * 1.1f + rightIncrementX2, balloonRect.centerY() - balloonRect.height() * 0.15f + rightIncrementY2,
                balloonRect.left + balloonRect.width() * 0.5f + rightIncrementX3, balloonRect.bottom + rightIncrementY3);

//        the results of the right final transformation
//        path.cubicTo(balloonRect.left + balloonRect.width() * 1.23f, balloonRect.centerY() - balloonRect.height() * 0.45f,
//                balloonRect.left + balloonRect.width() * 1.13f, balloonRect.centerY() + balloonRect.height() * 0.35f,
//                balloonRect.left + balloonRect.width() * 0.5f, balloonRect.bottom);

        return path;
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
        invalidateSelf();
    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        mPaint.setColorFilter(cf);
        invalidateSelf();
    }

    @Override
    public void setStrokeWidth(float strokeWidth) {
        super.setStrokeWidth(strokeWidth);
        mPaint.setStrokeWidth(strokeWidth);
        invalidateSelf();
    }

    @Override
    public void reset() {
    }
}

 

4、接下来就是如何使用的问题,首先我们需要在layout中定一个Imageview,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/balloon_view"
        android:background="#ffd4d9da"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
         />
</LinearLayout>

 

5、然后在相关的Activity中实现动画的播放和停止,使用事例如下:

private LoadingDrawable mBalloonDrawable;
private ImageView mIvBalloon;

@Override

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    mIvBalloon = (ImageView) findViewById(R.id.balloon_view);

    mBalloonDrawable = new LoadingDrawable(new BalloonLoadingRenderer(this));

    mIvBalloon.setImageDrawable(mBalloonDrawable);
}

@Override
protected void onStop() {
    super.onStop();
    mBalloonDrawable.stop();
}

@Override
protected void onStart() {
    super.onStart();
    mBalloonDrawable.start();
}

 

6、最后小编双手奉上源码的下载地址:http://download.csdn.net/detail/zhimingshangyan/9542044
7、能够理解的大神麻烦给我讲讲实现的原理,谢谢~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息