前言
这叫一个坑,搞了半天图片都显示不出来。
给出翻译的中文文档
正文
看了一下Fresco文档后你肯定欲血沸腾,想赶紧试试它有多么的强大。于是,你直接将文档中这段代码复制到了layout中。
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="200dp"
android:layout_height="200dp"
fresco:placeholderImage="@drawable/my_drawable"
/>
显示的非常漂亮,内心BB一句,太爽了。然后你可能会这样想,要是能适应高度该多好,于是你把代码改成了这样。
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="200dp"
android:layout_height="wrap_content"
fresco:placeholderImage="@drawable/my_drawable"
/>
一运行傻眼了,图片呢?如果换成ImageView
的话应该是能够正常显示的呀?
实际情况是SimpleDraweeView
已经被成功加载了,只不过高度为0dp
而已,所以你自然就看不到了。
这里先直接说一下结论,后面再慢慢分析。
结论:(一下几个要求要同时达到,才能显示图片)
- 宽度或者高度,两者至少一个以上的测量规格(
MeasureSpec
)模式为MeasureSpec.EXACTLY
。换句话说,宽度或者高度,两个至少一个以上,被指定为match_parent
或者固定宽高值(例如:100dp
) - 宽度或者高度,当两者其中有一个被指定为
warp_content
时,必须在代码中为控件设置宽高比(draweeView.setAspectRatio(0.5F);
) - 不要使用
0dp
+layout_weight=1
的组合代替warp_content
,虽然在源码中有这样一句话// Note: wrap_content is supported for backwards compatibility, but should not be used.(warp_content是为了支持向后的兼容性,不应该被使用。)
如此这样,你的图片就能显示出来了:
draweeView.setAspectRatio(0.5F);
draweeView.setImageURI(Uri.parse("..."));
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/draweeView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
分析
问题一 为什么不显示图片
问题来了,为什么在如下情况下图片没有正常显示(只剩下薄薄的一层)?
这个肯定是和测量有关系了,看一下SimpleDraweeView
的onMeasure()
方法。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMeasureSpec.width = widthMeasureSpec;
mMeasureSpec.height = heightMeasureSpec;
// 下面这局,在没有调用aspect为0时,不会执行。
AspectRatioMeasure.updateMeasureSpec(
mMeasureSpec,
mAspectRatio,
getLayoutParams(),
getPaddingLeft() + getPaddingRight(),
getPaddingTop() + getPaddingBottom());
super.onMeasure(mMeasureSpec.width, mMeasureSpec.height);
}
并没有什么有价值的信息,跟到super.onMeasure()
中,会走到ImageView
的onMeasure()
方法,里面全部份大段的代码都是判断是否要根据比例来修改图片宽高,没有什么用,最后会执行以下的代码,我们好好分析一下:
int pleft = mPaddingLeft;
int pright = mPaddingRight;
int ptop = mPaddingTop;
int pbottom = mPaddingBottom;
...省略
else {
/* We are either don't want to preserve the drawables aspect ratio,
or we are not allowed to change view dimensions. Just measure in
the normal way.
*/
w += pleft + pright;
h += ptop + pbottom;
w = Math.max(w, getSuggestedMinimumWidth());
h = Math.max(h, getSuggestedMinimumHeight());
widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
}
setMeasuredDimension(widthSize, heightSize);
可以看到,在h
和getSuggestedMinimumHeight()
中取最大值再赋给h
,而h
之前的值是mPaddingBottom
不会很大,而getSuggestedMinimumHeight()
的值貌似是10dp
(记不清除了),反正两者都不大,取最大值之后的h
值,自然也不会大到哪里去!
接着执行了以下代码,
heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
setMeasuredDimension(widthSize, heightSize);
简单的含义就是,从h
和高度的测量规格中,两者取小值,然后设置控件高度。
看到这里你就明白了为什么图片没有正常显示,一群小个子中取最小的,能高到哪里去?
问题二 为什么显示图片
那么,问题又来了,凭什么加上draweeView.setAspectRatio(0.5F);
设置了宽高比之后就可以显示了呢?
先上代码和效果图:
draweeView.setAspectRatio(1F); draweeView.setImageURI(Uri.parse("..."));
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/draweeView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
同样的,图片先不显示肯定和onMeasure()
有关系,我就再贴一次代码(不要打我),至于为什么设置了宽高比就走onMeasure()
可以看我的前一篇文章:Fresco分析
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
mMeasureSpec.width = widthMeasureSpec;
mMeasureSpec.height = heightMeasureSpec;
AspectRatioMeasure.updateMeasureSpec(
mMeasureSpec,
mAspectRatio,
getLayoutParams(),
getPaddingLeft() + getPaddingRight(),
getPaddingTop() + getPaddingBottom());
super.onMeasure(mMeasureSpec.width, mMeasureSpec.height);
}
当宽高比不为0时,就会执行AspectRatioMeasure.updateMeasureSpec()
方法,这是一个根据宽高比重测宽高的方法。
方法代码如下:
public static void updateMeasureSpec(
Spec spec,
float aspectRatio,
@Nullable ViewGroup.LayoutParams layoutParams,
int widthPadding,
int heightPadding) {
...省略代码
if (shouldAdjust(layoutParams.height)) {
// 获取父控件期望的宽的测量宽度
int widthSpecSize = View.MeasureSpec.getSize(spec.width);
// 根据父控件期望的宽的测量宽度和宽高比计算出咱们期望高的高度
int desiredHeight = (int) ((widthSpecSize - widthPadding) / aspectRatio + heightPadding);
// 期望的高度与父控件期望的高度两者取小的
int resolvedHeight = View.resolveSize(desiredHeight, spec.height);
// 最后重设高的测量规格
spec.height = View.MeasureSpec.makeMeasureSpec(resolvedHeight, View.MeasureSpec.EXACTLY);
}
...省略代码
}
private static boolean shouldAdjust(int layoutDimension) {
// Note: wrap_content is supported for backwards compatibility, but should not be used.
return layoutDimension == 0 || layoutDimension == ViewGroup.LayoutParams.WRAP_CONTENT;
}
记住此时在layout我们控件的宽设置的是wrap_content
。首先会执行shouldAdjust()
方法,该方法在高度参数为0或者wrap_content
返回true
,这时会进入if
中。
在if中,首先拿到测量宽度,在根据比例拿到期望的高度。接下来是最关键的一句View.resolveSize(desiredHeight, spec.height);
,在期望的高度和父控件建议的高度之中,取最小的值。在resolvesSize()
代码中,可以看到在MeasureSpec.AT_MOST
分支中如果期望高度不大于父控件建议的高度,则将size
作为了最后的返回结果,即是将期望的高度作为最后的结果返回。
这时,宽度和高度就有了,剩下的就是布局事情了,相信不用我多说。最后,至于为什么会走到MeasureSpec.AT_MOST
分支,我只想告诉你,嘿嘿,你猜呀,你猜呀!
问题三 为什么设置0dp+layout_weight=1不好使?
在参看这篇文章以前,你肯定页看了一些其他的博客,无以不是告诉你,如果想使用宽高比,那么你应该使用0dp
+layout_weight=1
的方式。
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/draweeView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
但是不行的是,没用呀,没用呀,完全没用呀,图片还是出不来。这里,我猜测的原因是layout_weight
参数并没有起到作用,导致高度为0dp
时,引起了一系列逗逼的结果。
假设,layout_weight
没生效,那么高度的值为0dp
,其测量模式是MeasureSpec.EXACTLY
,在resolveSizeAndStat()
中的MeasureSpec.EXACTLY
分支中,直接将specSize
作为结果了,而specSize
的值,恰恰是0dp
。悲剧。
最后
欢迎各位拍砖交流
Me Github : https://github.com/biezhihua