LoadingDrawable
LoadingDrawable
LoadingDrawable是github上挺火的一个项目, 通过自定义Drawable来实现各式各样的Loading动画. 现已经有多种有意思的动画效果, 可以直接用在自己项目中, 或者仿照他的做法实现自己的loading动画.
LoadingDrawable使用
凡是好用的东西一般都使用非常简单, 当然这个也不例外, 只要有一个ImageView, 在代码中创建一个需要的LoadingDrawable, 再将drawable设给ImageView就可以了.
mIvMaterial= (ImageView) findViewById(R.id.material_view);
//使用自己需要的LoadingRenderer
mMaterialDrawable=newLoadingDrawable(newMaterialLoadingRenderer(this));
mIvMaterial.setImageDrawable(mMaterialDrawable);
LoadingDrawable分析
概述
LoadingDrawable通过自定义一个Drawable将不同的动画画出来, 其中的对于不同的动画对应不同的LoadingRender
, 他们都是LoadingRender
的子类, 分别重写了不同的计算和绘制的方法以实现不同的效果. 下面先看看他所涉及的类:
真的是很简捷清晰, 高亮是Drawable的子类,
render
包下是对不同动画的渲染器, 其中的LoadingRender
是基类, 实现了基本的逻辑和定义绘制计算接口. 上面的几个包就是具体的动画实现.
LoadingDrawable
直接来看LoadingDrawable的实现:
//LoadingDrawable继承自Drawable, 可以自定义不用交互的可见控件
//实现Animatable接口, 他就成为一个动画, 可以在合适的时机显示或者停止
public class LoadingDrawable extends Drawable implements Animatable {
private LoadingRenderer mLoadingRender;
//定义一个Callback, 并将其传递给Render, 负责更新当前视图, 将其传递给Render可以避免Render去持有过多的引用
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);
}
};
//构造方法, 将Render保存在Drawable中, 方便后面将所有的计算绘制任务交给他
public LoadingDrawable(LoadingRenderer loadingRender) {
this.mLoadingRender = loadingRender;
this.mLoadingRender.setCallback(mCallback);
}
//直接将绘制的任务交给Render去做, 后面的好多方法也是类似的, 直接给Render去处理
@Override
public void draw(Canvas canvas) {
mLoadingRender.draw(canvas, getBounds());
}我是我
/*...省略部分代码...*/
}
代码量不大, 主要是将任务交给Render处理, 其中使用Callback的思想要学习一下.
LoadingRenderer
最核心的部分, 连接LoadingDrawable与各个具体动画, 规范各种动画接口的类, 就是LoadingRenderer
, 下面我们看看他都做了什么.
public abstract class LoadingRenderer {
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();
//这里的start其实就是start渲染动画
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) {
//就是从这里把计算的任务与绘制的任务分开, 动画这部分只负责计算
//真正的画出来是在draw里面
computeRender((float) animation.getAnimatedValue());
//通过CallBack通知Drable更新
invalidateSelf();
}
});
}
/*....省略部分代码.....*/
public void setDuration(long duration) {
this.mDuration = duration;
mRenderAnimator.setDuration(mDuration);
}
}
LoadingRender对各种不同的动画进行了抽象, 将动画的计算和绘制拆分出来, 十分有利于后面不同动画的实现, 另外还对一些默认参数进行设置. 真正的计算和绘制都在下面的子类中, 我们分析一个MaterialLoadingRenderer
.
MaterialLoadingRenderer
MaterialLoadingRenderer
是右上方的效果, 三种颜色交替过渡出现, 圆先变大半圆再变小半圆, 然后整体还在转动,,初分析感觉好难, 下面细细看其代码, 不得不说以前自己写的动画都是什么玩意儿啊..下面看源码
public void computeRender(float renderProgress) {
updateRingColor(renderProgress);
// Moving the start trim only occurs in the first 50% of a
// single ring animation
if (renderProgress <= START_TRIM_DURATION_OFFSET) {
//除定前半程比例, 这里相当于把一个动画分成了两个动画, 前半段是只移动头, 后半段只移动尾巴
//这里把原来一共的比例换算到前半段的时间上来
float startTrimProgress = renderProgress / START_TRIM_DURATION_OFFSET;
//要开始的角度. 原始角度加已经过了的角度
//向前伸出去的那个头,加原始角度(在一次动画中他是不变的, 等于上一次动画结束的地方)
//再加最大的多半圈乘扫过的比例
mStartDegrees = mOriginStartDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress);
}
// Moving the end trim starts after 50% of a single ring
// animation completes
if (renderProgress > START_TRIM_DURATION_OFFSET) {
//超过一半的比例/后半程比例, 同上
float endTrimProgress = (renderProgress - START_TRIM_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_TRIM_DURATION_OFFSET);
//尾巴所在的角度
mEndDegrees = mOriginEndDegrees + MAX_SWIPE_DEGREES * MATERIAL_INTERPOLATOR.getInterpolation(endTrimProgress);
}
//要显示的角度
if (Math.abs(mEndDegrees - mStartDegrees) > MIN_SWIPE_DEGREE) {
mSwipeDegrees = mEndDegrees - mStartDegrees;
}
//整个过程中画布一一直慢慢的转动
mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * renderProgress) + (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS));
//不知道干啥的....
mRotationIncrement = mOriginRotationIncrement + (MAX_ROTATION_INCREMENT * renderProgress);
}
过程中设了画笔的颜色, 颜色是动画前80%使用一个颜色, 后20%的时候使用
return ((startA + (int) (fraction * (endA - startA))) << 24)
| ((startR + (int) (fraction * (endR - startR))) << 16)
| ((startG + (int) (fraction * (endG - startG))) << 8)
| ((startB + (int) (fraction * (endB - startB))));
计算两个颜色的过渡色, 就会产生过渡的颜色变化.
后面的计算基本如注释描述. 直接看draw方法.
public void draw(Canvas canvas, Rect bounds) {
int saveCount = canvas.save();
//对Canvas进行转动, 产生画的过程中首尾都在转动的效果
canvas.rotate(mGroupRotation, bounds.exactCenterX(), bounds.exactCenterY());
RectF arcBounds = mTempBounds;
arcBounds.set(bounds);
arcBounds.inset(mStrokeInset, mStrokeInset);
mPaint.setColor(mCurrentColor);
//绘制弧线, 就是Loading的主体
canvas.drawArc(arcBounds, mStartDegrees, mSwipeDegrees, false, mPaint);
canvas.restoreToCount(saveCount);
}
基本上是拿一到计算的数据进行绘制就可以了, 记得每次都要将canvas进行restore
. 别的方法就是与动画相关的: 开始, 停止之类, 不再分析.
学习一个简单的Loading图就是这样, 后面会再分析一个使用图片的Loading图, 就可以根据自己的需求进行自定义各种动画了.
学习这个开源代码最大的收获就是感觉结果清晰, 每一个类, 每一个方法的责任都十分的明确, 十分值得我们学习.