您的位置:首页 > 其它

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

2015-11-05 17:46 561 查看

前言

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

给出翻译的中文文档

正文

看了一下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

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: