Facebook Fresco using wrap_content
Asked Answered
A

5

17

I got a bunch of drawables that I want to load using fresco, I want to use wrap_content size for those images, how can I do it in xml with fresco? Or if xml is not possible how do you do it in code?

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

The above code is not working unless I set a fixed size.

Archival answered 27/11, 2015 at 10:36 Comment(0)
L
43

I am part of the Fresco team and I was the one who made the design decision to not support wrap-content. The rationale is explained in the documentation. But in short, the problem is that you can't guarantee that the image will be available immediately (you may need to fetch it first) and that means that the view size would have to change once the image arrives. This is in most cases not desirable and you should probably rethink your UI.

Anyways, if you really really need/want to do that, you can do it like this:

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

I wrote this code from the top of my head, I haven't actually tested it. But the idea should be clear, and it should work with minor adjustments.

Lyford answered 3/12, 2015 at 20:12 Comment(12)
Tnx for you answer. I recommend you to reconsider your decision regardless wrap content support. In many cases people use drawables assets, those good for static images or some animations. Each application got some of those in one way or another. And it's nice knowing Frasco will take care my memory fear away. You can name it StaticDrawee if it helps.Archival
Yeah. In most cases wrap-content should not really be used as there are better alternatives, but then there are always going to be legitimate exceptions. I'll think of how to make this particular flow simpler.Lyford
offtopic for anyone coming here: If you have a recyclerview with lots of images, you display the full image(in another fragment/fullscreen activity) when the user taps on it, but until then set a fixed size for SimpleDraweeView (I think that's how everybody does it)Pied
Uhm, correct me if I'm wrong @plamenko, but this is not suitable for a recyclerview environment, since the draweeView reference can be replaced at any time, even before fresco finishes it's download, so, it wouldn't work.Social
@Ivan, If you mean on the scenario where the view get recycled and the previously set controller listener still holds the reference to that view, this should not be a problem. At the moment you recycle drawee view and set a new controller, the previously set controller will be detached immediately and its listener will receive onRelease and no other event. Since all this happens on the main thread (UI thread), there should be no synchronization issues either.Lyford
@Lyford First, thanks for your answer, after some modify, it works. But I also recommand you reconsider about this. This is not called "ugly" design, we have news post requirement, and every image should be scaled at equal ratio, and fill the width of the screen. Image's original size is controlled on our server side. And I believe a lot of people do this.Molecular
@cdytoby, if you have control over your images server side you can send both the uri and the aspect ratio to the client app. Then you can set matchParent for width, wrapContent for height and set the received aspect ratio programmatically at the same time you set the uri. The point is, you should not have to wait for image to be downloaded in order to obtain its dimensions and/or aspect ratio.Lyford
@Lyford aspect ratio is controlled on server side means: the aspect ratio is dynamic, but it will be horizontal image, width is longer than height, that's all. The height is dynamic. As I said, I still don't agree on disable "wrap_content", and it's not ugly.Molecular
@cdytoby, just to be clear, when I said ugly I wasn't referring to the aesthetics of yours or anyone's application. I was referring to the code necessary to achieve this behavior (the very code I provided above), meaning that it is more complicated than it could be. And that's by design. We intentionally didn't include that piece of code in Fresco API (even though we easily could've), because we don't want to make it easy for people to shoot themselves in the foot.Lyford
@cdytoby, ...continuing previous comment. I am not against wrap_content in general. For a content that is immediately available wrap_content is a perfectly reasonable thing to use. When it comes to asynchronous image loading, probably not so much (as explained in the documentation). It was, and still is, our conclusion that this feature is more dangerous than beneficial when it comes to asynchronous image loading. We know that there are legitimate exceptions and for those cases we provided a way to do it (as this answer demonstrates). It is okay for people to disagree :)Lyford
@plamenko, is it possible to Fresco set a warning if "wrap_content" is set as width or height?Paigepaik
that means that the view size would have to change once the image arrives. -- This can be useful when the image is a small icon without placeholder, and it does not affect other part of the layout.Scriptwriter
T
28

Based on @plamenko's answer, I made a custom view as follows:

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

You can include this class in the XML, an example usage:

<com.example.ui.views.WrapContentDraweeView
    android:id="@+id/simple_drawee_view"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    />
Tuff answered 28/3, 2016 at 22:12 Comment(7)
In order for this to work, you need to set either the width or the height, otherwise the aspect ratio is useless. So I don't think this will work, did you tested it?Archival
That's exactly what's mentioned in the class description. And I am using it. Works perfectly. Anyway, I edited the answer with the .xml as wellTuff
It is working for a lot of people. @meysam expand your comment or gist.github.com your code?Tuff
It's not working for me too. when set android:layout_width="match_parent" android:layout_height="wrap_content" I got only 1 line of 1px. then when I set wrap_content for both the WrapContentDraweeView shows nothing (on fresco 0.14.1)Pattiepattin
For those who found it not working, make sure you have override the correct setImageUri method. SimpleDraweeView has a number of setImageUri methods but only one of them is actually doing the setController work.Flew
this does not work, just shows a vertical line. but if i set either width or height to an actual number it works. i wish it could work with wrap_content for both. fresco 1.3.0Marxist
I'm using it inside a RecyclerView, not working in my case!Pelerine
D
1

In Kotlin you can try something like this :

       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

For me it was a solution in my RecyclerView because I was looking to set the layoutParams directly in my ViewHolder.

Dispensation answered 10/7, 2019 at 15:22 Comment(1)
Be careful! Potentially you are dividing by zeroAircraft
A
0

Found a solution by extending SimpleDraweeView, it allows me to use wrap_content and it works just fine! How ever I prevent you from setting the size in setContentView and the preview is not working, I be glad if could edit this answer to fix those.

Usage

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

Source code

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);
    }
}
Archival answered 27/11, 2015 at 10:37 Comment(0)
R
0

invoke updateWrapSize in onFinalImageSet

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());
            }
        }
    }
Reinhold answered 5/12, 2019 at 5:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.