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

前言

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

给出翻译的中文文档

正文

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

分析

问题一 为什么不显示图片

问题来了,为什么在如下情况下图片没有正常显示(只剩下薄薄的一层)?

这个肯定是和测量有关系了,看一下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"
/>

同样的,图片先不显示肯定和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

这里写图片描述

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值