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、能够理解的大神麻烦给我讲讲实现的原理,谢谢~