阅读本篇文章,建议先阅读:
Glide解析一:Glide整体流程
在解析Glide的整体流程时,我们发现Glide用的是Target而不是ImageView,为什么不直接在图片加载Request中持有ImageView,然后在图片加载完成时在onResourceReady方法中将图片设置ImageView呢?
其实Glide涉及到ImageView的工作量还是顶多的:
- 获取ImageView的大小
- 根据ImageView的生命周期调度重新加载、取消加载图片
- ImageView的动画效果
- ImageView加载过程中显示的默认图片、加载失败时显示的图片、加载成功时显示的是Bitmap还是Drawable以及动效效果
如果将这些功能都放到图片加载Request中实现,那么就违背了设计模式里的单一职责原则,而且会比较冗余而设Rquest变得复杂、混乱等不良后果。所以Glide秉承单一职责,Request只处理图片加载的逻辑;至于ImageView相关将其包装为Target,使用Target实现ImageView的逻辑。
我们先看下Target的主要相关的类图关系:
image
BaseTarget
它是一个抽象类,值定义了一个Request成员变量,用于保存与之关联的图片加载请求Request,方便在ImageView绑定到window时去加载图片或者从window卸载时取消图片加载:
public abstract class BaseTarget<Z> implements Target<Z> {
private Request request;
@Override
public void setRequest(@Nullable Request request) {
this.request = request;
}
@Override
@Nullable
public Request getRequest() {
return request;
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
// Do nothing.
}
@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
// Do nothing.
}
@Override
public void onLoadFailed(@Nullable Drawable errorDrawable) {
// Do nothing.
}
@Override
public void onStart() {
// Do nothing.
}
@Override
public void onStop() {
// Do nothing.
}
@Override
public void onDestroy() {
// Do nothing.
}
}
ViewTarget
ViewTarget主要做两件事:
a、获取ImageView的大小
在解析Glide整体流程时,有提出Glide是怎么获取ImageView的大小的?其实作为App开发工程师来说,获取一个View的大小无非是getWidth()、layoutParam.width,而当View还没绘制时是拿不到大小的,那么此时通过Activity的onWindowFocusChanged或者ViewTreeObserver来监听View的绘制完成时期在调用getWidth就可以拿到大小了。
Glide获取的时期是不太可能通过onWindowFocusChanged的了,剩下就只剩下ViewTreeObserver了,对的Glide就是通过ViewTreeObserver来获取的。我们看下其实现:
public void getSize(@NonNull SizeReadyCallback cb) {
//调用成员遍历sizeDeterminer的getSize()方法
sizeDeterminer.getSize(cb);
}
//SizeDeterminer.java ViewTarget的一个内部类
void getSize(@NonNull SizeReadyCallback cb) {
//获取当前view的宽度
int currentWidth = getTargetWidth();
//获取当前view的高度
int currentHeight = getTargetHeight();
if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
//如果View的大小大于0
//回调告知view的大小
cb.onSizeReady(currentWidth, currentHeight);
return;
}
if (!cbs.contains(cb)) {
//添加大小观察者
cbs.add(cb);
}
if (layoutListener == null) {
//获取ViwTreeObserver
ViewTreeObserver observer = view.getViewTreeObserver();
layoutListener = new SizeDeterminerLayoutListener(this);
//监听View的preDraw行为
observer.addOnPreDrawListener(layoutListener);
}
}
//获取宽度
private int getTargetWidth() {
int horizontalPadding = view.getPaddingLeft() + view.getPaddingRight();
LayoutParams layoutParams = view.getLayoutParams();
int layoutParamSize = layoutParams != null ? layoutParams.width : PENDING_SIZE;
return getTargetDimen(view.getWidth(), layoutParamSize, horizontalPadding);
}
//判断宽高是否大于0
private boolean isViewStateAndSizeValid(int width, int height) {
return isDimensionValid(width) && isDimensionValid(height);
}
//判断指定的大小是否大于0或者==Integer.MAX_VALUE
private boolean isDimensionValid(int size) {
return size > 0 || size == SIZE_ORIGINAL;
}
这段代码的核心思想就是先判断当前View的大小是否大于0,如果大于0就直接回调onSizeReady告知View大小已知;否则通过ViewTreeObserver监听View的onPreDraw行为来获取View的大小并告知监听者view的大小已经测量好:
public boolean onPreDraw() {
SizeDeterminer sizeDeterminer = sizeDeterminerRef.get();
if (sizeDeterminer != null) {
sizeDeterminer.checkCurrentDimens();
}
return true;
}
void checkCurrentDimens() {
if (cbs.isEmpty()) {
return;
}
//获取宽度
int currentWidth = getTargetWidth();
//获取高度
int currentHeight = getTargetHeight();
if (!isViewStateAndSizeValid(currentWidth, currentHeight)) {
//如果宽高小于0,表示view尚未测量好
return;
}
//回调通知监听则view的大小已经测量好
notifyCbs(currentWidth, currentHeight);
//移除监听者
clearCallbacksAndListener();
}
b、监听与Window的绑定关系
public final ViewTarget<T, Z> clearOnDetach() {
if (attachStateListener != null) {
return this;
}
//创建绑定状态监听者
attachStateListener = new OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
//绑定到window
resumeMyRequest();
}
@Override
public void onViewDetachedFromWindow(View v) {
//从window解绑
pauseMyRequest();
}
};
maybeAddAttachStateListener();
return this;
}
//设置view绑定window状态的监听者
private void maybeAddAttachStateListener() {
if (attachStateListener == null || isAttachStateListenerAdded) {
return;
}
//添加绑定状态监听者
view.addOnAttachStateChangeListener(attachStateListener);
isAttachStateListenerAdded = true;
}
@Synthetic void resumeMyRequest() {
//绑定window时
//获取图片加载对象request
Request request = getRequest();
if (request != null && request.isCleared()) {
//开始请求加载
request.begin();
}
}
@SuppressWarnings("WeakerAccess")
@Synthetic void pauseMyRequest() {
//从window解绑时
//获取图片加载对象request
Request request = getRequest();
if (request != null) {
isClearedByUs = true;
//取消图片加载
request.clear();
isClearedByUs = false;
}
}
通过监听view与window的绑定关系,进而调度图片加载发起加载请求或者取消加载请求。
ImageViewTarget
ImageViewTarget的主要工作有:
a、设置加载中的显示图片
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
setResourceInternal(null);
setDrawable(placeholder);
}
public void setDrawable(Drawable drawable) {
view.setImageDrawable(drawable);
}
设置加载中显示的图片很简单,先将原来的图片资源设置为空,在设置placeHolder为加载中显示的图片
b、设置加载失败的显示图片
public void onLoadFailed(@Nullable Drawable errorDrawable) {
super.onLoadFailed(errorDrawable);
setResourceInternal(null);
setDrawable(errorDrawable);
}
与加载中一样,加载失败时,先将原来的图片资源设置为空,在设置errorDrawable为加载失败显示的图片
c、图片加载成功的模板
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
if (transition == null || !transition.transition(resource, this)) {
//没有动画
//调用setResourceInternal设置加载成功的图片
setResourceInternal(resource);
} else {
//有动画,调用maybeUpdateAnimatable实现动效
maybeUpdateAnimatable(resource);
}
}
private void setResourceInternal(@Nullable Z resource) {
//调用setResource设置图片资源
setResource(resource);
//执行动效
maybeUpdateAnimatable(resource);
}
protected abstract void setResource(@Nullable Z resource);
在图片加载成功回调时,如果没有动效调用setResource设置加载成功的图片资源,而setResource是抽象方法,其实现是在DrawableImageViewTarget和BitmapImageVIewTarget来实现的;如果有动效则使用maybeUpdateAnimatable实现动效的逻辑。
d、动画的实现
上面的代码分析指导动效的实现是在函数maybeUpdateAnimatable中,我们看下其代码实现:
private void maybeUpdateAnimatable(@Nullable Z resource) {
if (resource instanceof Animatable) {
animatable = (Animatable) resource;
animatable.start();
} else {
animatable = null;
}
}
maybeUpdateAnimatable很简单,就是判断图片资源是否是Animatable的实现类,是的话就转换为Animatable,并调用start开始动效。
BitmapImageViewTarget
BitmapImageViewTarget就是以bitmap的形式设置图片的资源,在分析ImageViewTarget的时候就明确指出设置图片资源是在子类的setResource来实现,我们看下BitmapImageViewTarget的setResource方法:
protected void setResource(Bitmap resource) {
view.setImageBitmap(resource);
}
很简单就是调用setImageBitmap设置图片资源
DrawableImageViewTarget
DrawableImageViewTarget就是以bitmap的形式设置图片的资源,在分析ImageViewTarget的时候就明确指出设置图片资源是在子类的setResource来实现,我们看下DrawableImageViewTarget的setResource方法:
protected void setResource(@Nullable Drawable resource) {
view.setImageDrawable(resource);
}
很简单就是调用setImageDrawable设置图片资源
ok,总结下Target的核心:
- 通过ViewTreeObserver实现View的大小测量,测量到大小之后回到监听者的onSizeReady告知view的大小已经测量ok
- 通过监听View与Window的绑定关系发起加载图片的请求或者取消加载图片
- 设置加载中的显示图片
- 设置加载失败时显示的图片
- 设置加载成功时的图片、动效
综合起来Target的工作还是蛮多的,如果融入Request中那么就会导致Request更为复杂、乱,所以Glide单独Target模块来实现View的相关逻辑,体现了单一职责原则、高内聚低耦合的特性
网友评论