LoadingDrawable包含许多酷炫的加载动画,可以与任何View配合使用作为加载动画或者ProgressBar。项目的思路源于这个动画链接
Note: 这个项目正处于更新阶段, 将会不断有新的加载动画加入,欢迎关注我的Github, 获取LoadingDrawable的最新动态。
项目概要
LoadingDrawable继承Drawable(不懂的话可以先往后看)并实现接口Animatable编写的动画加载库,本项目采用策略模式(Strategy),构造函数必须传入LoadingRenderer的子类,并通过回调Callback与LoadingRenderer进行交互。LoadingRenderer主要负责LoadingDrawable的measure和draw。
Drawable
Drawable具有轻量级的、高效性、复用性强的特点。大家接触Drawable的最常见形式应该就是从res/drawable文件中读取Drawable😄😄😄。跟View的不同之处在于Drawable无法接收事件或以其他形式与用户交互。
Drawable提供了一些通用的控制绘制的方法。如下:
setBounds(Rect) 决定Drawable的绘制位置和大小, 所有的Drawable都应该遵循这个规则,Drawable一般都是通过缩放的形式的返回setBounds(Rect)所指定的大小。此外一般都是通过getIntrinsicHeight()和getIntrinsicWidth()设置首选大小。
getPadding(Rect) 决定Drawable的内间距。一般用于绘制在Drawable里面的内容需要放到确定位置处,而不是开始位置的情况。
setState(int[]) 决定客户端哪些状态可绘制,如“focused”, "selected"等, 有些Drawables可以根据选定的状态修改其绘制的图像。定义StateListDrawable(xml中使用selector元素定义)时,我常用“selected”设置状态, 主要因为并不是所有的View都有“checked”状态。
setLevel(int) 允许客户端提供一个单一连续控制器进行修改当前正在展示的Drawable,比如电池水平或进度。有些Drawables可以根据当前level修改其绘制的图像。
Drawable.Callback 一般用于Drawable动画的回调控制。所有的Drawable子类都应该支持这个类,否则动画将无法在View上正常工作(View是实现了这个接口与Drawable进行交互的)。此外,这个类也是LoadingDrawable和LoadingRenderer之间交互的纽带。
Animatable
Animatable 是Android针对支持Drawable动画设计的接口。熟为人知的AnimationDrawable就是实现这个接口编写的。
Animatable只提供了三个抽象方法。 如下:
isRunning() 表示动画是否正在运行。
start() 启动Drawable的动画。
stop() 停止Drawable的动画。
LoadingDrawable
LoadingDrawable继承Drawable并实现接口Animatable编写的动画加载库。这个库涉及了自定义Drawable时需要重写的基本方法和编写动画的基本框架,LoadingDrawable像是一本书,叙述着动画知识的篇章,LoadingDrawable也像是一个收集库, 囊括着各种酷炫的动画。
LoadingDrawable实现了什么 ?
圆形跳动系列
SwapLoadingRenderer
GuardLoadingRenderer
DanceLoadingRenderer
CollisionLoadingRenderer
-
圆形滚动系列
- GearLoadingRenderer
- WhorlLoadingRenderer
- LevelLoadingRenderer
- MaterialLoadingRenderer
-
风景系列
- DayNightLoadingRenderer
- ElectricFanLoadingRenderer
-
动物系列
- FishLoadingRenderer
- GhostsEyeLoadingRenderer
-
物品系列
- BalloonLoadingRenderer
- WaterBottleLoadingRenderer
LoadingDrawable 涉及了什么知识 ?
1. Canvas的常见用法
2. Path的基本用法
3. PathMeasure的基本用法
4. Path之贝塞尔曲线
5. 贝赛尔曲线公式 (只需知道线性公式,二次方公式,三次方公式即可)
LoadingDrawable 核心代码有哪些 ?
public class LoadingDrawable extends Drawable implements Animatable {
private LoadingRenderer mLoadingRender;
private final Callback mCallback = new Callback() {
@Override
public void invalidateDrawable(Drawable d) {
invalidateSelf();
}
@Override
public void scheduleDrawable(Drawable d, Runnable what, long when) {
scheduleSelf(what, when);
}
@Override
public void unscheduleDrawable(Drawable d, Runnable what) {
unscheduleSelf(what);
}
};
public LoadingDrawable(LoadingRenderer loadingRender) {
this.mLoadingRender = loadingRender;
this.mLoadingRender.setCallback(mCallback);
}
@Override
public void draw(Canvas canvas) {
mLoadingRender.draw(canvas, getBounds());
}
@Override
public void setAlpha(int alpha) {
mLoadingRender.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
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();
}
@Override
public int getIntrinsicWidth() {
return (int) mLoadingRender.getWidth();
}
}
LoadingDrawable内部的实现完全委托给了一个LoadingRenderer类型的成员变量, 但并不是代理模式, 我只是将LoadingDrawable 内部的行为统一的抽取到LoadingRenderer内部,便于子类继承进行统一的实现。我设计LoadingDrawable的思路源于对Android源码MaterialProgressDrawable的分析, 当初感觉MaterialProgressDrawable内部实现挺不错,就是感觉代码很乱,我就进行了一次整理, 就产生了LoadingDrawable的雏形。 接下来就看一下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);
}
}
mWidth,mHeight决定着Drawable的首选大小(可能会被拉伸,比如ImageView设置不同的ScaleType或者作为View的背景的时候),mStrokeWidth和mCenterRadius是便于统一当初Circle系列的Paint,不应该被提取到基类,后期会优化掉这些变量。
mRenderAnimator 是ValueAnimator类型的变量, 是LoadingRenderer渲染动画的触发器! 在onAnimationUpdate()方法里调用了computeRender(float)以及invalidateSelf()。computeRender(float)负责计算当前所要绘制内容的位置和大小以及色值等。invalidateSelf() 通过回调mCallback调用invalidateDrawable(null)触发LoadingDrawable重新绘制。 LoadingDrawable的重新绘制又会调用LoadingRenderer的draw(Canvas, Rect)方法,然后子类draw(Canvas, Rect)方法通过computeRender(float)计算的数据实现绘制。
LoadingDrawable 相关的源码分析文档有哪些 ?
Circle系列源码解析
Fish源码解析
期待你参与LoadingDrawable源码分析。 互相学习,互相进步 !!!
LoadingDrawable 实现效果图是什么样 ?
LoadingDrawable 如何使用 ?
在java中使用
LoadingView.setLoadingRenderer(LoadingRenderer);
在xml中使用
<app.dinus.com.loadingdrawable.LoadingView
android:id="@+id/level_view"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#fff1c02e"
app:loading_renderer="LevelLoadingRenderer"/>
关于我
我喜欢Android, 喜欢开源, 喜欢做动画, 会不定期开源一些有用的项目。
我会第一时间在微博分享我在Github上面开源的项目,欢迎关注我的微博 dinus_developer
源码地址:传送门