Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Predefined item height before image arrives #1011

Closed
AngleV opened this issue Feb 29, 2016 · 49 comments
Closed

Predefined item height before image arrives #1011

AngleV opened this issue Feb 29, 2016 · 49 comments
Labels

Comments

@AngleV
Copy link

AngleV commented Feb 29, 2016

How should i set up Glide and my xml for each item if i all ready know the heights of images.
Until know i set my ImageView to wrap_content

<ImageView
    android:id="@+id/item_image_img"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:adjustViewBounds="true"
    android:contentDescription="@string/img_content"
    android:scaleType="fitXY"/>

That works fine but as soon as image arrives it stretches the placeholder and the scrolling jumps giving an awful experience to user

@TWiStErRob
Copy link
Collaborator

I think you're looking for this: #864 (comment)
The stretching happens because you're using fitXY, take a look at this: http://etcodehome.blogspot.hu/2011/05/android-imageview-scaletype-samples.html
Android's TransitionDrawable doesn't support cross-fading between images with different aspect ratios (see #363), so you should set it up accordingly.

@AngleV
Copy link
Author

AngleV commented Feb 29, 2016

sorry but i'm a bit confused.

My placeholder is just a gray color.

.placeholder(new ColorDrawable(mFragment.getResources().getColor(R.color.light_gray)))

inside a LinearLayoutManager.
as user scrolls recyclerview items appear but height of each item here should be the same as the image that we are expect.
Images are displayed fine as they arrived but i need to get rid of this annoying layout changes of items.

@maheshwarLigade
Copy link

When you are using the android:adjustViewBounds="true" don't use the scaleType. It will resolve your issue.

@AngleV
Copy link
Author

AngleV commented Feb 29, 2016

@maheshwarLigade
This does not resolve the issue.
The items inside RecyclerView has smaller height. At the time image appears then the item's height increases.

@maheshwarLigade
Copy link

use the RecyclerView with WrapLayout Manager

@AngleV
Copy link
Author

AngleV commented Feb 29, 2016

@maheshwarLigade
even with the WrapLayoutManager same thing happens. Item's height its almost zero

@TWiStErRob
Copy link
Collaborator

Can you share your relevant (anything dealing with the imageview in the holder and layout) adapter code please?

@AngleV
Copy link
Author

AngleV commented Feb 29, 2016

@TWiStErRob

The layout of the item

<?xml version="1.0" encoding="utf-8"?>
    <android.support.v7.widget.CardView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/container"
        app:cardCornerRadius="0dp"
        android:background="@color/light_gray"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

            <ImageView
                android:id="@+id/item_image_img"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:adjustViewBounds="true"
                tools:src="@drawable/image"
                android:contentDescription="@string/img_content"
                android:scaleType="fitXY" />

</android.support.v7.widget.CardView>

and on bind item

Glide.with(mFragment)
    .load(image.getImageSrc(mScreenWidth))
    .fitCenter().override(image.getWidth(),image.getHeight())
    .placeholder(new ColorDrawable(mFragment.getResources().getColor(R.color.light_gray)))
    .crossFade()
    .diskCacheStrategy(DiskCacheStrategy.RESULT)
    .listener(new RequestListener<String, GlideDrawable>() {
        @Override
        public boolean onException(Exception e, String model, Target<GlideDrawable> target, boolean isFirstResource) {
            return false;
        }

        @Override
        public boolean onResourceReady(GlideDrawable resource, String model, Target<GlideDrawable> target, boolean isFromMemoryCache, boolean isFirstResource) {
            if (android.os.Build.VERSION.SDK_INT >= 21){
                    String transition = Constants.TRANS_NAME + getAdapterPosition();
                    mImageView.setTransitionName(transition);
            }

            return false;
        }
    })
    .into(mImageView);

@TWiStErRob
Copy link
Collaborator

Wait, you said "it stretches the placeholder", how do you know it is stretched if it's a single color?

Is there anything else interacting with the variable image? The jumpiness likely come from the fact that the view is reused and the old size is remembered, and when the new load finishes it has to re-layout. You must tell the view what size it should be beforehand so Android can layout with the right size the first time (on scroll). Did you try the percent layout I mentioned in my first comment?

@AngleV
Copy link
Author

AngleV commented Feb 29, 2016

@TWiStErRob
Sorry i meant the item's height is being stretched (resized).
Yes i tried with the percent layout and each item's height is almost zero.As soon as the image loads it increases the height of the percent layout

<android.support.percent.PercentFrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/item_image_img"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:adjustViewBounds="true"
        android:contentDescription="@string/img_content"
        app:layout_heightPercent="100%"/>

</android.support.percent.PercentFrameLayout>
PercentFrameLayout.LayoutParams layoutParams = (PercentFrameLayout.LayoutParams) mImageView .getLayoutParams();
layoutParams.getPercentLayoutInfo().aspectRatio = image.getWidth() / image.getHeight();
mImageView.setLayoutParams(layoutParams);

Glide.with(mFragment)
        .load(image.getImageSrc(mScreenWidth))
        .fitCenter().override(image.getWidth(),image.getHeight())
        .placeholder(new ColorDrawable(mFragment.getResources().getColor(R.color.light_gray)))
        .crossFade()
        .diskCacheStrategy(DiskCacheStrategy.RESULT)                    
        .into(mImageView);

And no nothing else interact with the ImageView.
You said "You must tell the view what size it should be beforehand so Android can layout with the right size the first time."

How can i do that ?

@TWiStErRob
Copy link
Collaborator

Through the layout system. Try to remove the extra layout params from ImageView to match @bryanstern's code, maybe they conflict.

@AngleV
Copy link
Author

AngleV commented Feb 29, 2016

Tried that allready.
i can see only the percentagelayout container and no image :(

@TWiStErRob
Copy link
Collaborator

How about integer division? ;)

@AngleV
Copy link
Author

AngleV commented Feb 29, 2016

@TWiStErRob

That did the trick. Now it works ;) Each item has the correct size but there are two issues.

Firstly, fast scrolling is painfully choppy. It is ridiculous not be able to achieve a smooth scroll with different item heights using a RecyclerView. Take a look at the Facebook app. When viewing albums you can see different height images and the scroll is smooth as butter. No matter how fast you scroll.

Secondly, first time image size is correct but if i scroll back again it has wrong height.
I am totally brain smashed right now :(

@TWiStErRob
Copy link
Collaborator

@bryanstern / @montanax as people who admittedly made this work, do you have any ideas here?

@TWiStErRob
Copy link
Collaborator

You're using app:layout_heightPercent and that should be widthPercent to fill the staggered grid's column and calculate height based on aspect (disclaimer: I never used percent, but it sounds reasonable and @bryanstern also said this works)

If it doesn't help try removing most of your extras and see if a bare-bone works

remove:

  • crossFade, placeholder, fitCenter, override
  • layout_width, layout_height, scaleType, adjustViewBounds

add:

  • .dontAnimate()

@bryanstern
Copy link

I think the ImageView xml is to blame, specifically the layout_height, layout_width, adjustViewBounds. Also, if you want constant width with a height based on the aspect ratio, the width percentage should be 100%. I would try:

<android.support.percent.PercentFrameLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/container"
  android:layout_width="match_parent"
  android:layout_height="wrap_content">

  <!--suppress AndroidDomInspection -->
  <ImageView
    android:id="@+id/item_image_img"
    android:contentDescription="@string/img_content"
    app:layout_widthPercent="100%"/>

</android.support.percent.PercentFrameLayout>
// use float division
PercentFrameLayout.LayoutParams layoutParams = (PercentFrameLayout.LayoutParams) mImageView .getLayoutParams();
            layoutParams.getPercentLayoutInfo().aspectRatio = (float) image.getWidth() / (float)  image.getHeight();
            mImageView.setLayoutParams(layoutParams);

// no need to override the height because the layout can determine this
Glide.with(mFragment)
                    .load(image.getImageSrc(mScreenWidth))
                    .placeholder(new ColorDrawable(mFragment.getResources().getColor(R.color.light_gray)))
                    .crossFade()
                    .diskCacheStrategy(DiskCacheStrategy.RESULT)                    
                    .into(mImageView);

This is pretty much exactly what I'm doing for my adapter that provides images of varying heights, but a constant width to RecyclerView with a StaggeredGridLayout. This has worked really well for me (no stutters, etc...)

@bryanstern
Copy link

Just refreshed and saw @TWiStErRob's post, which is pretty much my recommendation.

@TWiStErRob
Copy link
Collaborator

What about layout_heightPercent VS layout_widthPercent? The doc suggest that:

You can also make one dimension be a fraction of the other by setting only width or height and using layout_aspectRatio for the second one to be calculated automatically

which I read as

android:layout_width="match_parent"
  • setting the aspect ratio in code should work.

@AngleV
Copy link
Author

AngleV commented Feb 29, 2016

@TWiStErRob / @bryanstern
let me apply suggested changes and i'll get back to you shortly

@bryanstern
Copy link

@TWiStErRob
Using the scenario where you want the width to fill it's parent and the height to maintain the aspect ratio, I'd do the following (but you should be able to switch width and height in my examples for the reverse scenario).

I think having both layout_widthPercent="100%" and layout_width="match_parent" would not be in conflict with each other, but I did have problems if I also set layout_height="wrap_content". Since setting both layout_widthPercent and layout_width seemed redundant and the documentation examples show only using layout_widthPercent, I opted to only use layout_widthPercent.

@AngleV
Copy link
Author

AngleV commented Feb 29, 2016

@bryanstern

it didn't work at all.
With the provided code i get a zero height size PercentFrameLayout and no image at all.

@TWiStErRob
Copy link
Collaborator

Did you use width or height percent, because it looks like he edited his comment.

@AngleV
Copy link
Author

AngleV commented Feb 29, 2016

@TWiStErRob

oops Yes changing the app:layout_heightPercent="100%" to app:layout_widthPercent="100%"/> FINALLY it works !!!
Scroll now is smooth as sweet candy.
BUT if i scroll back again the size of item has change. Also something irrelevant with all this is that if i scroll back again image is not there, Glide reloads it again. I noticed that with .override that thing is not happening. images are staying there.

@TWiStErRob
Copy link
Collaborator

Hmm, maybe Glide doesn't like the absence or the automatic values set by the percent layout. Try adding match_parent(s); or call imageView.layout(0, 0, 0, 0) before setting the params to reset the size so Glide couldn't read it.

@AngleV
Copy link
Author

AngleV commented Feb 29, 2016

neither match_parent nor layout(0,0,0,0) works...
Scrolling up and down sometimes image is fine but item height is bigger.
Some other time item height is fine but image is not matching parent... i am sick of it

@TWiStErRob
Copy link
Collaborator

But you said it works with override... I guess you could use that. The only thing to be careful is that the resulting image size will match the override dimensions, so using the original size (for example 2304x1536 for digital images) is not recommended as it takes up too much memory on ALL devices. Try to calculate the grid width roughly (screen size / 2?) and calculate a nice fit based on the aspect to save some memory.

@AngleV
Copy link
Author

AngleV commented Feb 29, 2016

@TWiStErRob

by setting override it solves the issue of keeping the image there. Thats ok
The problem is that each time i scroll back the height of container does not keep the correct height as the first time.. The image appears fine but the height of Percentage is not the correct one.

Also server returns resized images so there is no problem with memory.

@trantuthien
Copy link

@TWiStErRob have you sloved this issue? I have the same problem.

@TWiStErRob
Copy link
Collaborator

I made a sample app with Staggered Grid and pre-known image sizes:
https://github.com/TWiStErRob/glide-support/tree/master/src/glide3/java/com/bumptech/glide/supportapp/github/_864_staggered_grid

I think in your case the last thing missing might be: #864 (comment)

@TWiStErRob
Copy link
Collaborator

@AngleV did you managed to figure this out?

@AngleV
Copy link
Author

AngleV commented Jul 17, 2016

@TWiStErRob

Yes using the following code

layout code

   <FrameLayout
            android:id="@+id/item_image_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <ImageView
                android:id="@+id/item_image_img"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:adjustViewBounds="true"
                tools:ignore="ContentDescription" />

        </FrameLayout>

inside adapter

            mImageView.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, (int) (mScreenWidth / mItems.get(position).getRatio())));

            Glide.with(mContext)
                    .load(mItems.get(position).getImage(mScreenWidth))
                    .override(mScreenWidth, mItems.get(position).getHeight())
                    .placeholder(new ColorDrawable(ContextCompat.getColor(mContext, R.color.light_gray)))
                    .crossFade()
                    .diskCacheStrategy(DiskCacheStrategy.SOURCE)
                    .into(mImageView);

it works perfect... Since i know all ready the ratio of each image...
The only issue that remains is that sometimes i got some small jitters on scrolling as glide loads the image on my View... related to this #984

Never succeeded to tweak this so that to make it work perfectly smooth.

@TWiStErRob
Copy link
Collaborator

Do you really need that override? I think the jumping is caused by mismatching sizes:

  • FrameLayout.LayoutParams.MATCH_PARENT ?= mScreenWidth
    left side may contain padding/margin, while right definitely don't (unless you incorporated it)
  • mScreenWidth / mItems.get(position).getRatio()) ?= mItems.get(position).getHeight()
    the left side is the size that i'll be laid out, and because of adjustViewBounds it's possible that it'll jump a little. The override causes the .fitCenter() transformation to downsize the image to a different size than your layout.

Try to remove it, as you already told Glide the size via layout params; or at least log the sizes to see if they always match.

@AngleV
Copy link
Author

AngleV commented Jul 17, 2016

The reason that i am setting ovveride is that i am using endless scroll and as soon as user reaches the end of scroll and a new data comes in i noticed that without ovveride the last image of previous load flashes a little.. Using the ovveride i got rid of the flashing....

@TWiStErRob
Copy link
Collaborator

TWiStErRob commented Jul 17, 2016

That flash probably means that the memory cache was not hit and it's reloading from disk. Try to double-check the numbers, I'm curious if they match. I suggest using my advanced logging listener it gives you all the numbers Glide is seeing.

@AngleV
Copy link
Author

AngleV commented Jul 17, 2016

Ohh nice advanced logging listener....
As soon as i check it i will let you know !!

@AngleV
Copy link
Author

AngleV commented Jul 17, 2016

indeed number does not match... log reports

07-17 16:23:00.151 31164-31164/com.bs.test V/GLIDE: .onResourceReady(GlideBitmapDrawable@a6f652c(1006x1341@ARGB_8888), http://www.............com/photo/2016062817105410207420489724852&w=360&type=raw, Target for: ForegroundImageView{ade60e9 V.ED..C.. ......ID 0,0-1440,1919 #7f100148 app:id/item_image_img}(params=1440x1919->size=1440x1919), async, first)

what i am doing wrong ?

@TWiStErRob
Copy link
Collaborator

Hmm, try to check what you actually pass in to override, because what I see here is that your view has size in both directions (which is weird because it should be params=-1x1919->size=1440x1919). Your resource is smaller than that (1006x1341), so no resizing happens. You're requesting w=360; I'm guessing that's dp, because you got back an ~1080px image.

@AngleV
Copy link
Author

AngleV commented Jul 17, 2016

@TWiStErRob
even without the override i get the same numbers.
Yes w=360 is dp multiplied by 2.8 in server side and i get the 1008px image...if i try to fetch 1440px image glide takes about 3 with 4 secs to decode the image.

@TWiStErRob
Copy link
Collaborator

TWiStErRob commented Jul 17, 2016

Try to replace (int) with Math.round, because that 1919 looks really weird. 1440x1920 would look better, maybe that's why it's slow. The server gives you a 1px taller image, and Glide has to fit it in. I'd don't think odd pixels sizes are good in general. This may also mess up your ratios, and cause the jump.

@AngleV
Copy link
Author

AngleV commented Jul 17, 2016

i think that did the trick...
But what i don't understand is why since my resource is 1006x1341 glide gives ->size=1440x1919 as size ?

@TWiStErRob
Copy link
Collaborator

Glide loads a source image (whatever the size) into a target. The target size is read from the view, or override. That param and size are both for the view. The image's (Bitmap's) size is right after the BitmapDrawable. You set the view size via layout params, so that was exact by the view framework. Since you're loading "perfect" sized images, I think you should use Target.SIZE_ORIGINAL in the override to prevent any resizing on Glide's part, or not use override at all, if you managed to get the view size match the source's ratio.

@AngleV
Copy link
Author

AngleV commented Jul 19, 2016

@TWiStErRob

To be honest i do not fully understand how glide works. Too much science for me.
Since i can manage the response returned by the server i know the width, height and ratio of each image.

All images are 4:3 ratio. it can be landscape or portrait.

So my question is how should i fix my xml layout ?
Should i use width/height = "match_parent" or "wrap_content" ?
Should i use ScaleType ?
Should i use adjustviewbounds ?

and on glide part...
Should i use override ?

my goal is to display just a gray placeholder with exact size of each incoming photo and let glide do its magic and display the image.

Whatever i do never get a perfect smooth scroll as for example instagram app has.

Sorry about all these questions but by now i have tried everything...
Scroll is almost perfect but sometimes i can spot some small jumbs when i scroll.

@Voyz
Copy link

Voyz commented Oct 8, 2016

@TWiStErRob, @AngleV

Any update on small jumps problem? I'm having a similar setup, though using scaleType="fitCenter" and glide's .override to set the dimensions exactly to available space, yet somehow the small jumps on scroll still happen.

I also tried
mIvPhoto.layout(mIvPhoto.getLeft(), mIvPhoto.getTop(), w, h);
or
mIvPhoto.getLayoutParams().height = h;
where w and h are the dimensions of available space and mIvPhoto is the ImageView that I pass to Glide as Target. No success either.

I notice that the height and/or width of the bitmap returned by Glide sometimes mismatches the height requested by the .override. It usually only a few pixels (such as in AngleV's response) - could this be the reason for jumps? We lay the height manually to a certain value before the request and Glide resizes it by these few pixels of difference once the resource is ready?

@MaxHastings
Copy link

MaxHastings commented Jul 16, 2017

Here's my solution that worked for me. There's no jumping while scrolling up or down. It requires you to set the aspect ratio of your imageView before loading with Glide. Here are some code snippets.

 final ConstraintLayout.LayoutParams lpt = (ConstraintLayout.LayoutParams) 
 holder.imageView.getLayoutParams();
 lpt.dimensionRatio = "w," + width + ":" + height;
 holder.imageView.setLayoutParams(lpt);

    GlideApp.with(context)
            .load(url)
            .into(holder.imageView);

    <android.support.constraint.ConstraintLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:adjustViewBounds="true"
            android:src="@mipmap/ic_launcher"
            app:layout_constraintDimensionRatio="w,1:1" />
    </android.support.constraint.ConstraintLayout>

@sajastu
Copy link

sajastu commented Jul 25, 2017

@maxwell10206 Does the solution really work for your? Would you please give us some explanations? Since it does not work in my case. I think, since you assign zero to ImageView's height and width, it totally doesn't show the images.

@MaxHastings
Copy link

MaxHastings commented Jul 25, 2017

@sajastu Are you using a Constraint layout as the parent view for the Imageview? The child views within a constraint layout with 0dp as their layout_width and height are actually equal to match_parent. You may want to study Constraint layouts and how they work first before using my code.

@kwong93
Copy link

kwong93 commented Feb 17, 2018

I was trying to figure out how to do this with coordiantor without percent layout. I also wanted to have the progress bar circle centered where the image would take the space. This works for me:

        <android.support.constraint.ConstraintLayout
            android:id="@+id/constraint_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <ImageView
                android:id="@+id/full_image"
                android:layout_width="0dp"
                android:layout_height="0dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent" />

            <ProgressBar
                android:id="@+id/full_image_progress"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />
        </android.support.constraint.ConstraintLayout>

You must set the layout params before you load the image with Glide

val PREDEFINED_WIDTH = 123
val PREDEFINED_HEIGHT = 456

val layoutParams = full_image.layoutParams as ConstraintLayout.LayoutParams
layoutParams.dimensionRatio = "H," + PREDEFINED_WIDTH  + ":" + PREDEFINED_HEIGHT
full_image.layoutParams = layoutParams

Glide.with(full_image).load(processedUrl).into(full_image)

@movisis
Copy link

movisis commented Jun 26, 2018

@kwong93 this is the final answer. Thank you very much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

10 participants