Facebook Fresco с использованием 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"/>

Приведенный выше код не работает, если я не установлю фиксированный размер.


person Ilya Gazman    schedule 27.11.2015    source источник


Ответы (5)


Я являюсь частью команды Fresco, и именно я принял дизайнерское решение не поддерживать обертку контента. Обоснование объясняется в документации. Но, вкратце, проблема в том, что вы не можете гарантировать, что изображение будет доступно немедленно (вам может потребоваться сначала его получить), а это означает, что размер представления должен измениться после прибытия изображения. В большинстве случаев это нежелательно, и вам, вероятно, следует переосмыслить свой пользовательский интерфейс.

В любом случае, если вам действительно нужно/хотите это сделать, вы можете сделать это так:

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

Я написал этот код из головы, я на самом деле не тестировал его. Но идея должна быть ясной, и она должна работать с небольшими корректировками.

person plamenko    schedule 03.12.2015
comment
Спасибо за ответ. Я рекомендую вам пересмотреть свое решение, независимо от поддержки контента. Во многих случаях люди используют ресурсы для рисования, которые хороши для статических изображений или некоторых анимаций. Каждое приложение так или иначе получило некоторые из них. И приятно знать, что Фраско избавит меня от страха перед памятью. Вы можете назвать его StaticDrawee, если это поможет. - person Ilya Gazman; 03.12.2015
comment
Ага. В большинстве случаев обертку не следует использовать, так как есть лучшие альтернативы, но всегда будут законные исключения. Я подумаю, как упростить этот конкретный поток. - person plamenko; 04.12.2015
comment
оффтоп для всех, кто приходит сюда: если у вас есть recyclerview с большим количеством изображений, вы отображаете полное изображение (в другом фрагменте/полноэкранном действии), когда пользователь нажимает на него, но до тех пор устанавливайте фиксированный размер для SimpleDraweeView (я думаю, что так все так делают) - person Tudor; 22.02.2016
comment
Хм, поправьте меня, если я ошибаюсь, @plamenko, но это не подходит для среды recyclerview, так как ссылка на draweeView может быть заменена в любое время, даже до того, как fresco завершит загрузку, поэтому это не сработает. - person Ivan; 18.03.2016
comment
@Ivan, если вы имеете в виду сценарий, в котором представление перерабатывается, а ранее установленный прослушиватель контроллера по-прежнему содержит ссылку на это представление, это не должно быть проблемой. В тот момент, когда вы перерабатываете представление Drawee и устанавливаете новый контроллер, ранее установленный контроллер будет немедленно отсоединен, а его слушатель получит onRelease и никакое другое событие. Поскольку все это происходит в основном потоке (UI-потоке), проблем с синхронизацией тоже быть не должно. - person plamenko; 20.03.2016
comment
@plamenko Во-первых, спасибо за ваш ответ, после некоторых изменений он работает. Но я также рекомендую вам пересмотреть это. Это не называется уродливым дизайном, у нас есть требование публикации новостей, и каждое изображение должно масштабироваться в одинаковом соотношении и заполнять ширину экрана. Исходный размер изображения контролируется на стороне нашего сервера. И я верю, что многие люди делают это. - person cdytoby; 18.05.2016
comment
@cdytoby, если у вас есть контроль над серверной частью изображений, вы можете отправить клиентскому приложению как uri, так и соотношение сторон. Затем вы можете установить matchParent для ширины, wrapContent для высоты и программно установить полученное соотношение сторон одновременно с установкой uri. Дело в том, что вам не нужно ждать загрузки изображения, чтобы получить его размеры и/или соотношение сторон. - person plamenko; 18.05.2016
comment
Соотношение сторон @plamenko контролируется на стороне сервера, это означает: соотношение сторон динамическое, но это будет горизонтальное изображение, ширина больше высоты, вот и все. Высота динамичная. Как я уже сказал, я все еще не согласен с отключением wrap_content, и это не безобразно. - person cdytoby; 20.05.2016
comment
@cdytoby, просто для ясности: когда я сказал «уродливый», я не имел в виду эстетику вашего или чьего-либо приложения. Я имел в виду код, необходимый для достижения такого поведения (тот самый код, который я привел выше), имея в виду, что он сложнее, чем мог бы быть. И это по дизайну. Мы намеренно не включили этот фрагмент кода в Fresco API (хотя вполне могли бы это сделать), потому что мы не хотим, чтобы люди легко стреляли себе в ногу. - person plamenko; 21.05.2016
comment
@cdytoby, ...продолжая предыдущий комментарий. Я не против wrap_content в целом. Для контента, который доступен немедленно, вполне разумно использовать wrap_content. Когда дело доходит до асинхронной загрузки изображений, вероятно, не так много (как объяснено в документации). Мы пришли к выводу, что эта функция скорее опасна, чем полезна, когда речь идет об асинхронной загрузке изображений. Мы знаем, что есть законные исключения, и для этих случаев мы предоставили способ сделать это (как показывает этот ответ). Это нормально, когда люди не согласны :) - person plamenko; 21.05.2016
comment
@plamenko, возможно ли, чтобы Fresco выдавала предупреждение, если для wrap_content установлено значение ширины или высоты? - person gustavoknz; 24.03.2018

Основываясь на ответе @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"
    />
person vedant    schedule 28.03.2016
comment
Чтобы это работало, вам нужно установить либо ширину, либо высоту, иначе соотношение сторон бесполезно. Так что я не думаю, что это сработает, вы проверяли это? - person Ilya Gazman; 29.03.2016
comment
Это именно то, что упоминается в описании класса. И я использую его. Работает отлично. В любом случае, я также отредактировал ответ с помощью .xml - person vedant; 29.03.2016
comment
Это работает для многих людей. @meysam раскройте свой комментарий или gist.github.com ваш код? - person vedant; 01.11.2016
comment
У меня тоже не работает. когда установлено android:layout_width=match_parent android:layout_height=wrap_content я получил только 1 строку 1px. затем, когда я устанавливаю wrap_content для обоих, WrapContentDraweeView ничего не показывает (на fresco 0.14.1) - person UmAnusorn; 24.11.2016
comment
Для тех, кто обнаружил, что это не работает, убедитесь, что вы переопределили правильный метод setImageUri. SimpleDraweeView имеет ряд setImageUri методов, но только один из них фактически выполняет setController работу. - person Sira Lam; 24.11.2017
comment
это не работает, просто показывает вертикальную линию. но если я установлю ширину или высоту на фактическое число, это сработает. я хотел бы, чтобы он мог работать с wrap_content для обоих. фреска 1.3.0 - person j2emanue; 08.07.2018
comment
Я использую его внутри RecyclerView, в моем случае это не работает! - person Malik Motani; 17.09.2018
comment
Спасибо, работает нормально. - person Alexios; 30.07.2021

В Котлине вы можете попробовать что-то вроде этого:

       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, потому что я хотел установить layoutParams непосредственно в моем ViewHolder.

person Jéwôm'    schedule 10.07.2019
comment
Будь осторожен! Возможно, вы делите на ноль - person Georgevik; 15.01.2020

Нашел решение, расширив 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);
    }
}
person Community    schedule 27.11.2015

вызвать updateWrapSize в 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());
            }
        }
    }
person legendmohe    schedule 05.12.2019