Facebook Fresco 如何使用 wrap_content?

17

我有一堆drawables,我想使用fresco加载它们,并希望对这些图像使用wrap_content大小,如何在xml中使用fresco实现?如果xml不可用,则如何在代码中实现?

<com.facebook.drawee.view.SimpleDraweeView
          android:id="@+id/myImage"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          fresco:placeholderImage="@mipmap/myImage"/>

除非我设置固定大小,否则上面的代码不起作用。

5个回答

42

我是Fresco团队的一员,也是决定不支持wrap-content的设计者。具体原因可以在文档中了解。简单来说,问题在于你无法保证图像会立即可用(可能需要先获取它),这意味着视图大小在图像到达后会发生改变。在大多数情况下,这是不可取的,因此您应该重新考虑UI设计。

无论如何,如果您确实非常需要/想要这样做,可以按照以下方式操作:

void updateViewSize(@Nullable ImageInfo imageInfo) {
  if (imageInfo != null) {
    draweeView.getLayoutParams().width = imageInfo.getWidth();
    draweeView.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
    draweeView.setAspectRatio((float) imageInfo.getWidth() / imageInfo.getHeight());
  }
}

ControllerListener listener = new BaseControllerListener {
    @Override
    public void onIntermediateImageSet(String id, @Nullable ImageInfo imageInfo) {
      updateViewSize(imageInfo);
    }

    @Override
    public void onFinalImageSet(String id, @Nullable ImageInfo imageInfo, @Nullable Animatable animatable) {
      updateViewSize(imageInfo);
    }
  };

DraweeController controller = draweeControllerBuilder
  .setUri(uri)
  .setControllerListener(listener)
  .build();
draweeView.setController(controller);

我凭记忆编写了这段代码,实际上我还没有对其进行测试。但是思路应该很清晰,并且只需要稍作调整就能够运行。


2
谢谢你的回答。我建议您重新考虑您的决定,无论是否支持包装内容。在许多情况下,人们使用可绘制的资源,这些资源适用于静态图像或某些动画。每个应用程序都以某种方式拥有其中一些。而且,知道Frasco会帮我处理内存问题真是太好了。如果有帮助的话,您可以将其命名为StaticDrawee。 - Ilya Gazman
是的,在大多数情况下,应该避免使用wrap-content,因为有更好的替代方案,但总会有合理的例外。我会考虑如何使这个特定的流程更简单。 - plamenko
1
@Ivan,如果你指的是视图被回收并且之前设置的控制器监听器仍然持有该视图的引用的情况,这应该不是问题。目前,当您回收drawee视图并设置新的控制器时,先前设置的控制器将立即被分离,并且其监听器将接收到onRelease事件和其他任何事件。由于所有这些都发生在主线程(UI线程)上,因此也不应该存在同步问题。 - plamenko
@plamenko 首先,感谢您的回答,经过一些修改,它可以工作了。但我也建议您重新考虑一下这个问题。这不是所谓的“丑陋”设计,我们有新闻发布要求,每张图片都应按等比例缩放,并填充屏幕的宽度。图片的原始大小在我们的服务器端进行控制。我相信很多人都这样做。 - cdytoby
@cdytoby,...继续上一条评论。我并不反对使用wrap_content。对于立即可用的内容,使用wrap_content是完全合理的。但当涉及到异步图像加载时,可能就不太合适了(正如文档中所解释的那样)。 我们得出的结论是,在异步图像加载方面,这个特性比有益更加危险。我们知道有合法的例外情况,针对这些情况,我们提供了一种方法来实现它(正如这个答案所示)。 人们有不同的看法也是可以的 :) - plamenko
显示剩余7条评论

28

基于 @plamenko 的回答,我制作了一个自定义视图,如下所示:

/**
 * Works when either height or width is set to wrap_content
 * The view is resized based on the image fetched
 */
public class WrapContentDraweeView extends SimpleDraweeView {

    // we set a listener and update the view's aspect ratio depending on the loaded image
    private final ControllerListener listener = new BaseControllerListener<ImageInfo>() {
        @Override
        public void onIntermediateImageSet(String id, @Nullable ImageInfo imageInfo) {
            updateViewSize(imageInfo);
        }

        @Override
        public void onFinalImageSet(String id, @Nullable ImageInfo imageInfo, @Nullable Animatable animatable) {
            updateViewSize(imageInfo);
        }
    };

    public WrapContentDraweeView(Context context, GenericDraweeHierarchy hierarchy) {
        super(context, hierarchy);
    }

    public WrapContentDraweeView(Context context) {
        super(context);
    }

    public WrapContentDraweeView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public WrapContentDraweeView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public WrapContentDraweeView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    public void setImageURI(Uri uri, Object callerContext) {
        DraweeController controller = ((PipelineDraweeControllerBuilder)getControllerBuilder())
                .setControllerListener(listener)
                .setCallerContext(callerContext)
                .setUri(uri)
                .setOldController(getController())
                .build();
        setController(controller);
    }

    void updateViewSize(@Nullable ImageInfo imageInfo) {
        if (imageInfo != null) {
            setAspectRatio((float) imageInfo.getWidth() / imageInfo.getHeight());
        }
    }
}

您可以将该类包含在XML中,以下是一个使用示例:

<com.example.ui.views.WrapContentDraweeView
    android:id="@+id/simple_drawee_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    />

为了使其正常工作,您需要设置宽度或高度,否则纵横比就没有用了。所以我认为这不会起作用,您测试过吗? - Ilya Gazman
这正是课程描述中提到的内容。而且我正在使用它。运行得非常完美。无论如何,我已经编辑了答案,并加入了 .xml 文件。 - Vedant Agarwala
它对很多人有效。@meysam,您能否扩展一下您的评论或将您的代码放在gist.github.com上? - Vedant Agarwala
1
对于那些发现它无法工作的人,请确保您已经覆盖了正确的 setImageUri 方法。SimpleDraweeView 有许多 setImageUri 方法,但只有其中一个实际上执行了 setController 的工作。 - Sira Lam
我正在RecyclerView中使用它,但在我的情况下不起作用! - Maddy
显示剩余2条评论

1
在 Kotlin 中,您可以尝试这样做:

       val listener = object : BaseControllerListener<ImageInfo>() {
        override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
            super.onFinalImageSet(id, imageInfo, animatable)
            itemView.draweeGif.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
            itemView.draweeGif.aspectRatio = (imageInfo?.width?.toFloat() ?: 0.toFloat()) / (imageInfo?.height?.toFloat() ?: 0.toFloat())
        }
    }

    val controller = Fresco.newDraweeControllerBuilder()
        .setUri(uriGif)
        .setControllerListener(listener)
        .setAutoPlayAnimations(true)
        .build()
    itemView.draweeGif.controller = controller

对于我来说,在我的RecyclerView中它是一个解决方案,因为我想直接在我的ViewHolder中设置layoutParams。

小心!你有可能在除以零。 - Georgevik

0

通过扩展SimpleDraweeView找到了解决方案,它允许我使用wrap_content并且效果很好!但是我阻止你在setContentView中设置大小,预览也无法工作,如果您能编辑此答案以修复这些问题,我将不胜感激。

用法

<com.gazman.WrapContentDraweeView 
          android:id="@+id/myImage"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          fresco:placeholderImage="@mipmap/myImage"/>

源代码

public class WrapContentDraweeView extends SimpleDraweeView {

    private int outWidth;
    private int outHeight;

    public WrapContentDraweeView(Context context, GenericDraweeHierarchy hierarchy) {
        super(context, hierarchy);
    }

    public WrapContentDraweeView(Context context) {
        super(context);
    }

    public WrapContentDraweeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public WrapContentDraweeView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

    public WrapContentDraweeView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        if (attrs == null) {
            return;
        }

        TypedArray gdhAttrs = context.obtainStyledAttributes(
                attrs,
                R.styleable.GenericDraweeView);
        try {
            int placeholderId = gdhAttrs.getResourceId(
                    R.styleable.GenericDraweeView_placeholderImage,
                    0);
            if(placeholderId != 0){
                if(isInEditMode()){
                    setImageResource(placeholderId);
                }
                else {
                    loadSize(placeholderId, context.getResources());
                }
            }
        } finally {
            gdhAttrs.recycle();
        }
    }

    private void loadSize(int placeholderId, Resources resources) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(resources, placeholderId, options);
        outWidth = options.outWidth;
        outHeight = options.outHeight;
    }

    @Override
    public void setLayoutParams(ViewGroup.LayoutParams params) {
        params.width = outWidth;
        params.height = outHeight;
        super.setLayoutParams(params);
    }
}

0

onFinalImageSet中调用updateWrapSize

void updateWrapSize(@Nullable ImageInfo imageInfo) {
        if (imageInfo != null) {
            boolean wrapH = getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT;
            boolean wrapW = getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT;
            if (wrapH || wrapW) {
                if (wrapW && !wrapH) {
                    getLayoutParams().width = (int) (imageInfo.getWidth() * (float) getLayoutParams().height / imageInfo.getHeight());
                } else if (wrapH && !wrapW) {
                    getLayoutParams().height = (int) (imageInfo.getHeight() * (float) getLayoutParams().width / imageInfo.getWidth());
                } else {
                    getLayoutParams().width = ViewGroup.LayoutParams.WRAP_CONTENT;
                    getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
                }
                setAspectRatio((float) imageInfo.getWidth() / imageInfo.getHeight());
            }
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接