Fresco前传(3):之为什么图片不显示(坑爹的wrap_content)

时间:2022-06-09 18:44:20

前言

这叫一个坑,搞了半天图片都显示不出来。

给出翻译的中文文档

正文

看了一下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而已,所以你自然就看不到了。

这里先直接说一下结论,后面再慢慢分析。

结论:(一下几个要求要同时达到,才能显示图片)

  1. 宽度或者高度,两者至少一个以上的测量规格(MeasureSpec)模式为MeasureSpec.EXACTLY。换句话说,宽度或者高度,两个至少一个以上,被指定为match_parent或者固定宽高值(例如:100dp)
  2. 宽度或者高度,当两者其中有一个被指定为warp_content时,必须在代码中为控件设置宽高比(draweeView.setAspectRatio(0.5F);
  3. 不要使用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"
/>

分析

问题一 为什么不显示图片

问题来了,为什么在如下情况下图片没有正常显示(只剩下薄薄的一层)?
Fresco前传(3):之为什么图片不显示(坑爹的wrap_content)

这个肯定是和测量有关系了,看一下SimpleDraweeViewonMeasure()方法。

  @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()中,会走到ImageViewonMeasure()方法,里面全部份大段的代码都是判断是否要根据比例来修改图片宽高,没有什么用,最后会执行以下的代码,我们好好分析一下:


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);

可以看到,在hgetSuggestedMinimumHeight()中取最大值再赋给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"
/>

Fresco前传(3):之为什么图片不显示(坑爹的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

Fresco前传(3):之为什么图片不显示(坑爹的wrap_content)