使用 wrap_content 的 Facebook Fresco

Facebook Fresco using wrap_content

我有一堆要使用 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"/>

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

通过扩展 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);
    }
}

我是 Fresco 团队的一员,是我做出了不支持 wrap-content 的设计决定。 documentation 中解释了基本原理。但简而言之,问题是您不能保证图像将立即可用(您可能需要先获取它),这意味着一旦图像到达,视图大小就必须改变。这在大多数情况下是不可取的,您可能应该重新考虑您的 UI.

无论如何,如果你真的need/want要那样做,你可以这样做:

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

这段代码是我凭空写的,我还没有实际测试过。但是思路应该很清晰,稍微调整一下就可以了。

根据@plamenko的回答,我做了一个自定义view如下:

/**
 * 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());
        }
    }
}

您可以将此 class 包含在 XML 中,示例用法:

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

在 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 中设置布局参数。

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