概述
有了前面几篇博客的预备知识,现在就可以来学习下自定义Drawble了。这篇主要是介绍一个开源项目的自定义Drawble的实现,主要是没有看到效果无法讲清楚原理。下一篇再介绍原理。
开源项目
开元项目地址
<a>https://github.com/dinuscxj/LoadingDrawable</a>
项目效果
这篇会介绍右上角的代码,注释都会写在代码中。先来看下XML中的入口,自定义了一个View。
<app.dinus.com.loadingdrawable.LoadingView
android:id="@+id/material_view"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#ff2a8cc8"
app:loading_renderer="MaterialLoadingRenderer"/>
看下自定义的View,创建了Drawable和Render,这是是先动画效果最终要的类。其中Drawble是android自带的,BitmapDrawble就是其子类,主要是绘制的用处。Render是作者自定义的。用来实现效果。
public class LoadingView extends ImageView {
private LoadingDrawable mLoadingDrawable;
public LoadingView(Context context) {
super(context);
}
public LoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttrs(context, attrs);
}
private void initAttrs(Context context, AttributeSet attrs) {
try {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LoadingView);
int loadingRendererId = ta.getInt(R.styleable.LoadingView_loading_renderer, 0);
// 创建一个Render,这个是动画的主要实现类
LoadingRenderer loadingRenderer = LoadingRendererFactory.createLoadingRenderer(context, loadingRendererId);
// 创建Drawable,并关联Drawable
setLoadingRenderer(loadingRenderer);
ta.recycle();
} catch (Exception e) {
e.printStackTrace();
}
}
public void setLoadingRenderer(LoadingRenderer loadingRenderer) {
mLoadingDrawable = new LoadingDrawable(loadingRenderer);
setImageDrawable(mLoadingDrawable);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
startAnimation();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
stopAnimation();
}
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
if (visibility == View.VISIBLE) {
startAnimation();
} else {
stopAnimation();
}
}
// 让Drawable开始冻哈
private void startAnimation() {
if (mLoadingDrawable != null) {
mLoadingDrawable.start();
}
}
// 让Drawbale停止动画
private void stopAnimation() {
if (mLoadingDrawable != null) {
mLoadingDrawable.stop();
}
}
}
看下Drawble,这个类代码比较少,继承了Drawable,实现了Animatable接口。然后就是重写了一些方法。mCallback是Render通知Drawble重新绘制用的。start()和stop()方法中可以看到,实现动画效果的是Render。
public class LoadingDrawable extends Drawable implements Animatable {
private final LoadingRenderer mLoadingRender;
// 传递给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);
}
};
public LoadingDrawable(LoadingRenderer loadingRender) {
this.mLoadingRender = loadingRender;
// 设置回调
this.mLoadingRender.setCallback(mCallback);
}
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
// 改变绘制区域
this.mLoadingRender.setBounds(bounds);
}
@Override
public void draw(Canvas canvas) {
if (!getBounds().isEmpty()) {
// 调用Render的绘制方法
this.mLoadingRender.draw(canvas);
}
}
@Override
public void setAlpha(int alpha) {
// 设置透明度
this.mLoadingRender.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
// 设置过滤器
this.mLoadingRender.setColorFilter(cf);
}
// 获取不透明策略
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
// 开始
@Override
public void start() {
this.mLoadingRender.start();
}
// 结束
@Override
public void stop() {
this.mLoadingRender.stop();
}
// 是否在运行
@Override
public boolean isRunning() {
return this.mLoadingRender.isRunning();
}
// 获取默认的高度
@Override
public int getIntrinsicHeight() {
return (int) this.mLoadingRender.mHeight;
}
// 获取默认的宽度
@Override
public int getIntrinsicWidth() {
return (int) this.mLoadingRender.mWidth;
}
}
看下Render,这是一个抽象类。实现了主要的逻辑,就是创建了属性动画ValueAnimator ,实现开始和结束属性动画的方法。mAnimatorUpdateListener实现了属性动画的动画进度的监听。但是方法computeRender是在子类中去实现的。
// 所有着色器的父类
public abstract class LoadingRenderer {
// 动画持续时间
private static final long ANIMATION_DURATION = 1333;
// 默认图形的大小
private static final float DEFAULT_SIZE = 56.0f;
// 插值器的监听器
private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener
= new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 计算,子类实现
computeRender((float) animation.getAnimatedValue());
// 通知Drawble重新绘制
invalidateSelf();
}
};
/**
* Whenever {@link LoadingDrawable} boundary changes mBounds will be updated.
* More details you can see {@link LoadingDrawable#onBoundsChange(Rect)}
*/
protected final Rect mBounds = new Rect();
// 回调,通知Drawable
private Drawable.Callback mCallback;
// 属性动画,用来控制动画的进度
private ValueAnimator mRenderAnimator;
// 动画时长
protected long mDuration;
// 长宽
protected float mWidth;
protected float mHeight;
public LoadingRenderer(Context context) {
// 初始化参数
initParams(context);
// 设置动画
setupAnimators();
}
@Deprecated
protected void draw(Canvas canvas, Rect bounds) {
}
// 子类去实现
protected void draw(Canvas canvas) {
draw(canvas, mBounds);
}
// 子类去实现
protected abstract void computeRender(float renderProgress);
// 子类去实现
protected abstract void setAlpha(int alpha);
// 子类去实现
protected abstract void setColorFilter(ColorFilter cf);
protected abstract void reset();
// 设置监听
protected void addRenderListener(Animator.AnimatorListener animatorListener) {
mRenderAnimator.addListener(animatorListener);
}
// 开始属性动画
void start() {
reset();
mRenderAnimator.addUpdateListener(mAnimatorUpdateListener);
mRenderAnimator.setRepeatCount(ValueAnimator.INFINITE);
mRenderAnimator.setDuration(mDuration);
mRenderAnimator.start();
}
// 结束属性动画
void stop() {
// if I just call mRenderAnimator.end(),
// it will always call the method onAnimationUpdate(ValueAnimator animation)
// why ? if you know why please send email to me (dinus_developer@163.com)
mRenderAnimator.removeUpdateListener(mAnimatorUpdateListener);
mRenderAnimator.setRepeatCount(0);
mRenderAnimator.setDuration(0);
mRenderAnimator.end();
}
boolean isRunning() {
return mRenderAnimator.isRunning();
}
// 设置与Drawbale相关的回调
void setCallback(Drawable.Callback callback) {
this.mCallback = callback;
}
// 当bounds改变时,改变render的bounds
void setBounds(Rect bounds) {
mBounds.set(bounds);
}
private void initParams(Context context) {
// 设置长宽
mWidth = DensityUtil.dip2px(context, DEFAULT_SIZE);
mHeight = DensityUtil.dip2px(context, DEFAULT_SIZE);
// 1.333秒
mDuration = ANIMATION_DURATION;
}
// 初始化属性动画
private void setupAnimators() {
mRenderAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
mRenderAnimator.setRepeatCount(Animation.INFINITE);
mRenderAnimator.setRepeatMode(Animation.RESTART);
mRenderAnimator.setDuration(mDuration);
//fuck you! the default interpolator is AccelerateDecelerateInterpolator
mRenderAnimator.setInterpolator(new LinearInterpolator());
mRenderAnimator.addUpdateListener(mAnimatorUpdateListener);
}
// 调用Drawbale中的invalidate
private void invalidateSelf() {
mCallback.invalidateDrawable(null);
}
}
看下具体实现类MaterialLoadingRenderer。 draw方法中的canvas.drawArc(mTempBounds, mStartDegrees, mSwipeDegrees, false, mPaint);用绘制圆弧的方法实现了旋转的效果。逻辑是这样,首先根据属性动画的Update监听返回fraction值,然后根据这个值计算出圆弧的动画策略。如果进度在50%下,那么圆弧的头前进。如果大于50%,那么圆弧的尾前进。同时还自带一个缓慢旋转的效果。等到执行了一个周期(这里是5次旋转回到起点)就重新开始。还会在每一次动画效果中改变颜色。每一次计算后,将通知Drawble去重新绘制,随后会调用draw方法绘制。
public class MaterialLoadingRenderer extends LoadingRenderer {
private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
// 360
private static final int DEGREE_360 = 360;
// 循环周期里有5此swipe
private static final int NUM_POINTS = 5;
// 一次swipe的角度
private static final float MAX_SWIPE_DEGREES = 0.8f * DEGREE_360;
// 一个周期总的角度
private static final float FULL_GROUP_ROTATION = 3.0f * DEGREE_360;
// 进度的80%
private static final float COLOR_START_DELAY_OFFSET = 0.8f;
// 进度的100%
private static final float END_TRIM_DURATION_OFFSET = 1.0f;
// 进度的50%
private static final float START_TRIM_DURATION_OFFSET = 0.5f;
// 半径
private static final float DEFAULT_CENTER_RADIUS = 12.5f;
// 圈的宽度
private static final float DEFAULT_STROKE_WIDTH = 2.5f;
// 预设的颜色
private static final int[] DEFAULT_COLORS = new int[]{
Color.RED, Color.GREEN, Color.BLUE
};
// 画笔
private final Paint mPaint = new Paint();
// 绘制区域的矩形
private final RectF mTempBounds = new RectF();
// 设置动画的监听
private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationRepeat(Animator animator) {
super.onAnimationRepeat(animator);
// 重新设置初始值(mOriginEndDegrees,mOriginStartDegrees)
storeOriginals();
// 获取颜色数组的下一个颜色
goToNextColor();
// 重置开始值
mStartDegrees = mEndDegrees;
// 重置开始次数
mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
// 开始时设置次数为0
mRotationCount = 0;
}
};
// 颜色数组
private int[] mColors;
// 当前颜色的index
private int mColorIndex;
// 当前颜色
private int mCurrentColor;
// 需要拓展或者搜索的宽度
private float mStrokeInset;
// 一个周期内循环的次数,如再次回到起点需要5此次
private float mRotationCount;
// 在周期循环的基础上,还要加上不停的转动
private float mGroupRotation;
// 结束的角度
private float mEndDegrees;
// 开始的角度
private float mStartDegrees;
// 结束-开始
private float mSwipeDegrees;
// 初始的结束角度
private float mOriginEndDegrees;
// 初始的开始角度
private float mOriginStartDegrees;
// 圈的粗细
private float mStrokeWidth;
// 内径
private float mCenterRadius;
private MaterialLoadingRenderer(Context context) {
super(context);
// 初始化参数
init(context);
// 初始化画笔和绘制模式
setupPaint();
// 设置监听
addRenderListener(mAnimatorListener);
}
private void init(Context context) {
// 从dp变换成px
mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH);
mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS);
// 默认颜色数组
mColors = DEFAULT_COLORS;
// 设置数组中为0的颜色
setColorIndex(0);
// 计算需要扩大或者收缩的宽度
initStrokeInset(mWidth, mHeight);
}
private void setupPaint() {
mPaint.setAntiAlias(true);
mPaint.setStrokeWidth(mStrokeWidth);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeCap(Paint.Cap.ROUND);
}
@Override
protected void draw(Canvas canvas) {
// 将Canvas当前状态保存在堆栈
int saveCount = canvas.save();
// 设置绘制区域
mTempBounds.set(mBounds);
// 设置需要扩大或者收缩绘制区域
mTempBounds.inset(mStrokeInset, mStrokeInset);
// 画笔旋转一定角度,这样看上去会像是在转动
canvas.rotate(mGroupRotation, mTempBounds.centerX(), mTempBounds.centerY());
// 如果swipe角度不为0,绘制
if (mSwipeDegrees != 0) {
mPaint.setColor(mCurrentColor);
canvas.drawArc(mTempBounds, mStartDegrees, mSwipeDegrees, false, mPaint);
}
// 恢复为之前堆栈保存的Canvas状态,即旋转前的状态
canvas.restoreToCount(saveCount);
}
// 根据属性动画的Update来绘制
@Override
protected void computeRender(float renderProgress) {
// 刷新
updateRingColor(renderProgress);
// Moving the start trim only occurs in the first 50% of a single ring animation
// 如果进度还小于50%,那么改变开始的角度
if (renderProgress <= START_TRIM_DURATION_OFFSET) {
// 计算百分比
float startTrimProgress = renderProgress / START_TRIM_DURATION_OFFSET;
// 开始角度+一次swipe的最大角度*快出慢进插值器计算出来的值
mStartDegrees = mOriginStartDegrees + MAX_SWIPE_DEGREES
* MATERIAL_INTERPOLATOR.getInterpolation(startTrimProgress);
}
// Moving the end trim starts after 50% of a single ring animation completes
// 如果进度还大于50%,那么改变结束的角度
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);
}
// 计算swipe的值
if (Math.abs(mEndDegrees - mStartDegrees) > 0) {
mSwipeDegrees = mEndDegrees - mStartDegrees;
}
// 计算滚动角度
mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * renderProgress)
+ (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS));
}
@Override
protected void setAlpha(int alpha) {
// 设置透明度
mPaint.setAlpha(alpha);
}
@Override
protected void setColorFilter(ColorFilter cf) {
// 设置颜色过滤器
mPaint.setColorFilter(cf);
}
@Override
protected void reset() {
// 重置
resetOriginals();
}
private void setColorIndex(int index) {
// 设置当前颜色
mColorIndex = index;
mCurrentColor = mColors[mColorIndex];
}
private int getNextColor() {
// 获取下一个颜色
return mColors[getNextColorIndex()];
}
private int getNextColorIndex() {
return (mColorIndex + 1) % (mColors.length);
}
private void goToNextColor() {
// 跳到下一个颜色
setColorIndex(getNextColorIndex());
}
private void initStrokeInset(float width, float height) {
// 长宽中的最小值
float minSize = Math.min(width, height);
// 从长宽最小值中计算需要插入的宽度
float strokeInset = minSize / 2.0f - mCenterRadius;
// 从画笔粗细中计算
float minStrokeInset = (float) Math.ceil(mStrokeWidth / 2.0f);
// 取较大值
mStrokeInset = strokeInset < minStrokeInset ? minStrokeInset : strokeInset;
}
private void storeOriginals() {
mOriginEndDegrees = mEndDegrees;
mOriginStartDegrees = mEndDegrees;
}
private void resetOriginals() {
mOriginEndDegrees = 0;
mOriginStartDegrees = 0;
mEndDegrees = 0;
mStartDegrees = 0;
}
private int getStartingColor() {
return mColors[mColorIndex];
}
private void updateRingColor(float interpolatedTime) {
// 如果进度已经超过0.8,那么计算新的颜色
if (interpolatedTime > COLOR_START_DELAY_OFFSET) {
// 在剩余的20%中重新计算百分比,然后从当前颜色渐变到下一个颜色
mCurrentColor = evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET)
/ (1.0f - COLOR_START_DELAY_OFFSET), getStartingColor(), getNextColor());
}
}
// 计算过度颜色
private int evaluateColorChange(float fraction, int startValue, int endValue) {
int startA = (startValue >> 24) & 0xff;
int startR = (startValue >> 16) & 0xff;
int startG = (startValue >> 8) & 0xff;
int startB = startValue & 0xff;
int endA = (endValue >> 24) & 0xff;
int endR = (endValue >> 16) & 0xff;
int endG = (endValue >> 8) & 0xff;
int endB = endValue & 0xff;
return ((startA + (int) (fraction * (endA - startA))) << 24)
| ((startR + (int) (fraction * (endR - startR))) << 16)
| ((startG + (int) (fraction * (endG - startG))) << 8)
| ((startB + (int) (fraction * (endB - startB))));
}
private void apply(Builder builder) {
this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth;
this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight;
this.mStrokeWidth = builder.mStrokeWidth > 0 ? builder.mStrokeWidth : this.mStrokeWidth;
this.mCenterRadius = builder.mCenterRadius > 0 ? builder.mCenterRadius : this.mCenterRadius;
this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration;
this.mColors = builder.mColors != null && builder.mColors.length > 0 ? builder.mColors : this.mColors;
setColorIndex(0);
setupPaint();
initStrokeInset(this.mWidth, this.mHeight);
}
public static class Builder {
private Context mContext;
private int mWidth;
private int mHeight;
private int mStrokeWidth;
private int mCenterRadius;
private int mDuration;
private int[] mColors;
public Builder(Context mContext) {
this.mContext = mContext;
}
public Builder setWidth(int width) {
this.mWidth = width;
return this;
}
public Builder setHeight(int height) {
this.mHeight = height;
return this;
}
public Builder setStrokeWidth(int strokeWidth) {
this.mStrokeWidth = strokeWidth;
return this;
}
public Builder setCenterRadius(int centerRadius) {
this.mCenterRadius = centerRadius;
return this;
}
public Builder setDuration(int duration) {
this.mDuration = duration;
return this;
}
public Builder setColors(int[] colors) {
this.mColors = colors;
return this;
}
public MaterialLoadingRenderer build() {
MaterialLoadingRenderer loadingRenderer = new MaterialLoadingRenderer(mContext);
loadingRenderer.apply(this);
return loadingRenderer;
}
}
}
总结
这篇主要是一个开源框架的讲解,介绍了Drawable的实现。如果可能的话,那么下一篇将介绍自定义Drawable的实现步骤。