Android加载动画系列——WaterBottleLoading

Android加载动画系列——WaterBottleLoading

        这篇文章跟上一篇文章一样都是同一个作者,在此小编奉上原文地址:https://github.com/dinuscxj/LoadingDrawable

       小编本着利人利己的原则来写这篇文章,恳请各位读者老爷不要鄙视小编(可怜状~)。

       让我们先来看看效果图:

 

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

1、LoadingDrawable.java源码如下:

public class LoadingDrawable extends Drawable implementsAnimatable {
    private LoadingRenderermLoadingRender;

    private final Callback mCallback= new Callback() {
        @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(int alpha) {
        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、核心的WaterBottleLoadingRenderer源码如下:

public class WaterBottleLoadingRenderer extends LoadingRenderer {
    private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();

    private static final float DEFAULT_WIDTH = 200.0f;
    private static final float DEFAULT_HEIGHT = 150.0f;
    private static final float DEFAULT_STROKE_WIDTH = 1.5f;
    private static final float DEFAULT_BOTTLE_WIDTH = 30;
    private static final float DEFAULT_BOTTLE_HEIGHT = 43;
    private static final float WATER_LOWEST_POINT_TO_BOTTLENECK_DISTANCE = 30;

    private static final int DEFAULT_WAVE_COUNT = 5;
    private static final int DEFAULT_WATER_DROP_COUNT = 25;

    private static final int MAX_WATER_DROP_RADIUS = 5;
    private static final int MIN_WATER_DROP_RADIUS = 1;

    private static final int DEFAULT_BOTTLE_COLOR = Color.parseColor("#FFDAEBEB");
    private static final int DEFAULT_WATER_COLOR = Color.parseColor("#FF29E3F2");

    private static final float DEFAULT_TEXT_SIZE = 7.0f;

    private static final String LOADING_TEXT = "loading";

    private static final long ANIMATION_DURATION = 11111;

    private final Random mRandom = new Random();

    private final Paint mPaint = new Paint();
    private final RectF mCurrentBounds = new RectF();
    private final RectF mBottleBounds = new RectF();
    private final RectF mWaterBounds = new RectF();
    private final Rect mLoadingBounds = new Rect();
    private final List<WaterDropHolder> mWaterDropHolders = new ArrayList<>();

    private float mTextSize;
    private float mProgress;

    private float mBottleWidth;
    private float mBottleHeight;
    private float mWaterLowestPointToBottleneckDistance;

    private int mBottleColor;
    private int mWaterColor;

    private int mWaveCount;

    public WaterBottleLoadingRenderer(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;

        mBottleWidth = DEFAULT_BOTTLE_WIDTH * screenDensity;
        mBottleHeight = DEFAULT_BOTTLE_HEIGHT * screenDensity;
        mWaterLowestPointToBottleneckDistance = WATER_LOWEST_POINT_TO_BOTTLENECK_DISTANCE * screenDensity;

        mBottleColor = DEFAULT_BOTTLE_COLOR;
        mWaterColor = DEFAULT_WATER_COLOR;

        mWaveCount = DEFAULT_WAVE_COUNT;

        setDuration(ANIMATION_DURATION);
    }

    private void setupPaint() {
        mPaint.setAntiAlias(true);
        mPaint.setStrokeWidth(getStrokeWidth());
        mPaint.setStrokeJoin(Paint.Join.ROUND);
    }

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

        RectF arcBounds = mCurrentBounds;
        arcBounds.set(bounds);
        //draw bottle
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(mBottleColor);
        canvas.drawPath(createBottlePath(mBottleBounds), mPaint);

        //draw water
        mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mPaint.setColor(mWaterColor);
        canvas.drawPath(createWaterPath(mWaterBounds, mProgress), mPaint);

        //draw water drop
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mWaterColor);
        for (WaterDropHolder waterDropHolder : mWaterDropHolders) {
            if (waterDropHolder.mNeedDraw) {
                canvas.drawCircle(waterDropHolder.mInitX, waterDropHolder.mCurrentY, waterDropHolder.mRadius, mPaint);
            }
        }

        //draw loading text
        mPaint.setColor(mBottleColor);
        canvas.drawText(LOADING_TEXT, mBottleBounds.centerX() - mLoadingBounds.width() / 2.0f,
                mBottleBounds.bottom + mBottleBounds.height() * 0.2f, mPaint);
        canvas.restoreToCount(saveCount);
    }

    @Override
    public void computeRender(float renderProgress) {
        if (mCurrentBounds.width() <= 0) {
            return;
        }

        RectF arcBounds = mCurrentBounds;
        //compute gas tube bounds
        mBottleBounds.set(arcBounds.centerX() - mBottleWidth / 2.0f, arcBounds.centerY() - mBottleHeight / 2.0f,
                arcBounds.centerX() + mBottleWidth / 2.0f, arcBounds.centerY() + mBottleHeight / 2.0f);
        //compute pipe body bounds
        mWaterBounds.set(mBottleBounds.left + getStrokeWidth() * 1.5f, mBottleBounds.top + mWaterLowestPointToBottleneckDistance,
                mBottleBounds.right - getStrokeWidth() * 1.5f, mBottleBounds.bottom - getStrokeWidth() * 1.5f);

        //compute wave progress
        float totalWaveProgress = renderProgress * mWaveCount;
        float currentWaveProgress = totalWaveProgress - ((int)totalWaveProgress);

        if (currentWaveProgress > 0.5f) {
            mProgress = 1.0f - MATERIAL_INTERPOLATOR.getInterpolation((currentWaveProgress - 0.5f) * 2.0f);
        } else {
            mProgress = MATERIAL_INTERPOLATOR.getInterpolation(currentWaveProgress * 2.0f);
        }

        //init water drop holders
        if (mWaterDropHolders.isEmpty()) {
            initWaterDropHolders(mBottleBounds, mWaterBounds);
        }

        //compute the location of these water drops
        for (WaterDropHolder waterDropHolder : mWaterDropHolders) {
            if (waterDropHolder.mDelayDuration < renderProgress
                    && waterDropHolder.mDelayDuration + waterDropHolder.mDuration > renderProgress) {
                float riseProgress = (renderProgress - waterDropHolder.mDelayDuration) / waterDropHolder.mDuration;
                riseProgress = riseProgress < 0.5f ? riseProgress * 2.0f : 1.0f - (riseProgress - 0.5f) * 2.0f;
                waterDropHolder.mCurrentY = waterDropHolder.mInitY -
                        MATERIAL_INTERPOLATOR.getInterpolation(riseProgress) * waterDropHolder.mRiseHeight;
                waterDropHolder.mNeedDraw = true;
            } else {
                waterDropHolder.mNeedDraw = false;
            }
        }

        //measure loading text
        mPaint.setTextSize(mTextSize);
        mPaint.getTextBounds(LOADING_TEXT, 0, LOADING_TEXT.length(), mLoadingBounds);
    }

    private Path createBottlePath(RectF bottleRect) {
        float bottleneckWidth = bottleRect.width() * 0.3f;
        float bottleneckHeight = bottleRect.height() * 0.415f;
        float bottleneckDecorationWidth = bottleneckWidth * 1.1f;
        float bottleneckDecorationHeight = bottleneckHeight * 0.167f;

        Path path = new Path();
        //draw the left side of the bottleneck decoration
        path.moveTo(bottleRect.centerX() - bottleneckDecorationWidth * 0.5f, bottleRect.top);
        path.quadTo(bottleRect.centerX() - bottleneckDecorationWidth * 0.5f - bottleneckWidth * 0.15f, bottleRect.top + bottleneckDecorationHeight * 0.5f,
                bottleRect.centerX() - bottleneckWidth * 0.5f, bottleRect.top + bottleneckDecorationHeight);
        path.lineTo(bottleRect.centerX() - bottleneckWidth * 0.5f, bottleRect.top + bottleneckHeight);

        //draw the left side of the bottle's body
        float radius = (bottleRect.width() - getStrokeWidth()) / 2.0f;
        float centerY = bottleRect.bottom - 0.86f * radius;
        RectF bodyRect = new RectF(bottleRect.left, centerY - radius, bottleRect.right, centerY + radius);
        path.addArc(bodyRect, 255, -135);

        //draw the bottom of the bottle
        float bottleBottomWidth = bottleRect.width() / 2.0f;
        path.lineTo(bottleRect.centerX() - bottleBottomWidth / 2.0f, bottleRect.bottom);
        path.lineTo(bottleRect.centerX() + bottleBottomWidth / 2.0f, bottleRect.bottom);

        //draw the right side of the bottle's body
        path.addArc(bodyRect, 60, -135);

        //draw the right side of the bottleneck decoration
        path.lineTo(bottleRect.centerX() + bottleneckWidth * 0.5f, bottleRect.top + bottleneckDecorationHeight);
        path.quadTo(bottleRect.centerX() + bottleneckDecorationWidth * 0.5f + bottleneckWidth * 0.15f, bottleRect.top + bottleneckDecorationHeight * 0.5f,
                bottleRect.centerX() + bottleneckDecorationWidth * 0.5f, bottleRect.top);

        return path;
    }

    private Path createWaterPath(RectF waterRect, float progress) {
        Path path = new Path();

        path.moveTo(waterRect.left, waterRect.top);

        //Similar to the way draw the bottle's bottom sides
        float radius = (waterRect.width() - getStrokeWidth()) / 2.0f;
        float centerY = waterRect.bottom - 0.86f * radius;
        float bottleBottomWidth = waterRect.width() / 2.0f;
        RectF bodyRect = new RectF(waterRect.left, centerY - radius, waterRect.right, centerY + radius);

        path.addArc(bodyRect, 187.5f, -67.5f);
        path.lineTo(waterRect.centerX() - bottleBottomWidth / 2.0f, waterRect.bottom);
        path.lineTo(waterRect.centerX() + bottleBottomWidth / 2.0f, waterRect.bottom);
        path.addArc(bodyRect, 60, -67.5f);

        //draw the water waves
        float cubicXChangeSize = waterRect.width() * 0.35f * progress;
        float cubicYChangeSize = waterRect.height() * 1.2f * progress;

        path.cubicTo(waterRect.left + waterRect.width() * 0.80f - cubicXChangeSize, waterRect.top - waterRect.height() * 1.2f + cubicYChangeSize,
                waterRect.left + waterRect.width() * 0.55f - cubicXChangeSize, waterRect.top - cubicYChangeSize,
                waterRect.left, waterRect.top - getStrokeWidth() / 2.0f);

        path.lineTo(waterRect.left, waterRect.top);

        return path;
    }

    private void initWaterDropHolders(RectF bottleRect, RectF waterRect) {
        float bottleRadius = bottleRect.width() / 2.0f;
        float lowestWaterPointY = waterRect.top;
        float twoSidesInterval = 0.2f * bottleRect.width();
        float atLeastDelayDuration = 0.1f;

        float unitDuration = 0.1f;
        float delayDurationRange = 0.6f;
        int radiusRandomRange = MAX_WATER_DROP_RADIUS - MIN_WATER_DROP_RADIUS;
        float currentXRandomRange = bottleRect.width() * 0.6f;

        for (int i = 0; i < DEFAULT_WATER_DROP_COUNT; i++) {
            WaterDropHolder waterDropHolder = new WaterDropHolder();
            waterDropHolder.mRadius = MIN_WATER_DROP_RADIUS + mRandom.nextInt(radiusRandomRange);
            waterDropHolder.mInitX = bottleRect.left + twoSidesInterval + mRandom.nextFloat() * currentXRandomRange;
            waterDropHolder.mInitY = lowestWaterPointY + waterDropHolder.mRadius / 2.0f;
            waterDropHolder.mRiseHeight = getMaxRiseHeight(bottleRadius, waterDropHolder.mRadius, waterDropHolder.mInitX - bottleRect.left)
                    * (0.2f + 0.8f * mRandom.nextFloat());
            waterDropHolder.mDelayDuration = atLeastDelayDuration + mRandom.nextFloat() * delayDurationRange;
            waterDropHolder.mDuration = waterDropHolder.mRiseHeight / bottleRadius * unitDuration;

            mWaterDropHolders.add(waterDropHolder);
        }
    }

    private float getMaxRiseHeight(float bottleRadius, float waterDropRadius, float currentX) {
        float coordinateX = currentX - bottleRadius;
        float bottleneckRadius = bottleRadius * 0.3f;
        if (coordinateX - waterDropRadius > -bottleneckRadius
                && coordinateX + waterDropRadius < bottleneckRadius) {
            return bottleRadius * 2.0f;
        }

        return (float) (Math.sqrt(Math.pow(bottleRadius, 2.0f) - Math.pow(coordinateX, 2.0f)) - waterDropRadius);
    }

    @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() {
    }

    private class WaterDropHolder {
        public float mCurrentY;

        public float mInitX;
        public float mInitY;
        public float mDelayDuration;
        public float mRiseHeight;

        public float mRadius;
        public float mDuration;

        public boolean mNeedDraw;
    }
}
 

 

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/water_view"
        android:background="#ffd4d9da"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
</LinearLayout>
 

 

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

private LoadingDrawable mWaterDrawable;
private ImageView mIvWater;

@Override

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

    mIvWater = (ImageView) findViewById(R.id.water_view);

    mWaterDrawable = new LoadingDrawable(new WaterBottleLoadingRenderer(this));

    mIvWater.setImageDrawable(mWaterDrawable);
}

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

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

 

6、最后小编双手奉上源码的下载地址:http://download.csdn.net/detail/zhimingshangyan/9543007

7、能够理解的大神麻烦给我讲讲实现的原理,谢谢~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值