Glide: Scroll jumps

Created on 16 Feb 2016  路  10Comments  路  Source: bumptech/glide

I am using the following code to load images from urls.

//avatar loading
Glide.with(holder.itemView.getContext())
    .load((mImages.get(position)).getAuthorImage())
    .asBitmap()
    .placeholder(R.drawable.default_avatar)
    .fitCenter()
    .diskCacheStrategy(DiskCacheStrategy.RESULT)
    .into(holder.imageAuthorImage);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    String avatar_transition = Constants.AVATAR_TRANS_NAME + position;
    holder.imageAuthorImage.setTransitionName(avatar_transition);
}

holder.imageAuthorImage.setOnClickListener(new onAuthorClickListener(holder.imageAuthorImage, (ImagesViewHolder) holder, position));
holder.viewOnMap.setOnClickListener(new onViewOnMapClickListener(position));
holder.share.setOnClickListener(new onShareClickListener(position));

int ratio = (int) (((float) this.mScreenWidth) / mImages.get(position).getRatio());

//main photo loading
Glide.with(holder.itemView.getContext())
    .load((mImages.get(position)).getImageSrc(mScreenWidth))
    .diskCacheStrategy(DiskCacheStrategy.ALL)
    .override(mScreenWidth,ratio)
    .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(resource != null){
                Bitmap b = ((GlideBitmapDrawable) resource).getBitmap();
                if (!(b == null || b.isRecycled())) {
                    if (PaletteTransformation.getPalette(b) == null) {
                        Log.d("ImageAdapter", "generate palette-"+position);
                        Palette.from(b)
                                .maximumColorCount(15)
                                .resizeBitmapSize(Utils.optimalImageWidth(mScreenWidth))
                                .generate(new myAsyncPaletteListener(holder,image, b));
                    }
                    holder.itemView.setTag(b.getGenerationId());
                }
            }

            if (android.os.Build.VERSION.SDK_INT >= 21){
                String beach_transition = Constants.BEACH_TRANS_NAME + position;
                holder.imageView.setTransitionName(beach_transition);
            }
            return false;

        }
    })
    .into(holder.imageView);

holder.imageView.setMinimumHeight(ratio);
holder.imageView.setMaxHeight(ratio);
holder.imageView.setAdjustViewBounds(false);
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) holder.imageView.getLayoutParams();
layoutParams.height = ratio;
holder.imageView.setLayoutParams(layoutParams);

Each image may have different size especially in height.
The issue here is that when user flings the recyclerview has small jumps in views and it does not scroll smoothly.
Removing the loading of images then scrolling is fine.
Any ideas?

question stale

Most helpful comment

@pedropalb I think the problem is that the items are reused and their size is calculated async. The ImageView can't measure itself properly while the bitmap is not available so it'll show up as size 0. As soon as the bitmap is loaded it will re-measure itself and the layout will change likely causing the jump.

If you know the image sizes before loading you can try staggered grid + percent layout as seen here: https://github.com/TWiStErRob/glide-support/tree/master/src/glide3/java/com/bumptech/glide/supportapp/github/_864_staggered_grid

All 10 comments

The jumping is because your layout changes all the time.
First try to set up the imageView layout before calling into(), so that Glide sees those changes. This may allow you to remove the .override.
Another option is to set the XML to wrap_content and let the .override determine the view size.
But in any case I think it will jump because the height changes "randomly". Maybe a staggered grid would help?

Also note that the async palette loading may highlight the wrong image with the wrong color in case the item view was recycled and used for another image.

The xml is wrap_content. However i will try to set a staggered grid to see if it make any difference.
Any advise for the async palette loading ?

I'm in the process of contributing built-in advanced palette handling to Glide.

For simple integration use these:

Glide.with(this)
     .load(url)
     .asBitmap()
     .transcode(new PaletteBitmapTranscoder(context), PaletteBitmap.class)
     .into(new PaletteBitmapViewTarget(imageView) {
         @Override protected void setResource(PaletteBitmap resource) {
             super.setResource(resource);
             Palette palette = resource.palette; // do whatever you want with this
         }
     })
;

Sorry I wasn't clear about the XML, even if it's wrap_content there, you overwrite it every time with layoutParams.height, so it's "fixed" height at runtime.

This thing is driving me crazy. I'm struggling those days to eliminate those small lags but with no luck.
Any advise how can i optimize the item layout ?

<?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/main_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="5dp"
    android:layout_marginLeft="0dp"
    android:layout_marginRight="0dp"
    android:layout_marginTop="5dp"
    android:orientation="vertical"
    app:cardCornerRadius="0dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:orientation="vertical">

        <FrameLayout 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="match_parent"
            android:background="@color/light_gray"
            android:orientation="vertical">

            <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"
                android:scaleType="fitXY"
                tools:src="@drawable/header" />

            <FrameLayout
                android:id="@+id/item_image_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:clickable="true"
                android:foreground="?selectableItemBackgroundBorderless"
                android:orientation="vertical" />

        </FrameLayout>

        <RelativeLayout 
            android:id="@+id/item_text_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:paddingBottom="5dp"
            android:paddingLeft="10dp"
            android:paddingRight="10dp"
            android:paddingTop="5dp"
            android:visibility="visible">


            <ui.Views.CircleImageView
                android:id="@+id/item_author_image"
                android:layout_width="30dp"
                android:layout_height="30dp"
                android:layout_centerVertical="true"
                android:layout_gravity="center_vertical"
                android:layout_marginRight="5dp"
                android:clickable="true"
                android:foreground="?selectableItemBackgroundBorderless"
                android:scaleType="fitXY"
                android:src="@drawable/default_avatar"
                app:civ_border="true"
                app:civ_border_color="@color/avatar_border"
                app:civ_border_width="1dp" />


            <TextView
                android:id="@+id/item_author_name"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_gravity="center_vertical"
                android:layout_toRightOf="@+id/item_author_image"
                android:ellipsize="end"
                android:lines="1"
                android:textColor="@color/black_alpha80"
                android:textSize="13.0sp"
                android:textStyle="bold"
                tools:text="User name" />


            <TextView
                android:id="@+id/item_image_date"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_alignParentEnd="true"
                android:layout_centerVertical="true"
                android:layout_gravity="center_vertical"
                android:ellipsize="end"
                android:gravity="right"
                android:lines="1"
                android:textColor="@android:color/tertiary_text_light"
                android:textSize="11.0sp"
                tools:text="Jun 18, 2016" />


        </RelativeLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center_vertical"
            android:layout_marginBottom="5dp"
            android:layout_marginLeft="47dp"
            android:gravity="center_vertical"
            android:orientation="vertical"
            android:paddingRight="10dp"
            android:visibility="visible"
            tools:layout_height="wrap_content">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_gravity="center_vertical"
                android:gravity="center_vertical"
                android:orientation="horizontal"
                android:paddingBottom="5dp"
                android:paddingTop="5dp"
                tools:layout_height="wrap_content">

                <com.mikepenz.iconics.view.IconicsImageView
                    android:layout_width="10dp"
                    android:layout_height="10dp"
                    android:layout_marginRight="3dp"
                    android:contentDescription="@string/descr"
                    app:iiv_color="@color/colorPrimary"
                    app:iiv_icon="gmd-access"
                    tools:src="@drawable/avatar_change_photo" />

                <TextView
                    android:id="@+id/item_image_beach_name"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_gravity="center_vertical"
                    android:ellipsize="end"
                    android:lines="1"
                    android:textColor="@color/colorPrimary"
                    android:textSize="15.0sp"
                    tools:text="Kaladi" />

            </LinearLayout>


            <TextView
                android:id="@+id/item_image_comments"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginBottom="10dp"
                android:layout_marginLeft="15dp"
                android:ellipsize="end"
                android:maxLines="4"
                android:textColor="@android:color/primary_text_light_nodisable"
                android:textSize="13.0sp"
                tools:layout_height="wrap_content"
                tools:text="Amazing !!!" />


        </LinearLayout>

        <View
            android:id="@+id/divider"
            android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:background="@drawable/divider_shape" />

        <?xml version="1.0" encoding="utf-8"?>
        <RelativeLayout 
            android:layout_width="match_parent"
            android:layout_height="45dp"
            android:gravity="center_vertical">


            <View
                android:layout_width="match_parent"
                android:layout_height="1dp" />

            <LinearLayout
                android:id="@+id/item_view_favorite"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_marginLeft="6dp"
                android:background="?android:attr/selectableItemBackground"
                android:clickable="true"
                android:gravity="center"
                android:orientation="horizontal"
                android:padding="5dp">

                <com.mikepenz.iconics.view.IconicsImageView
                    android:id="@+id/like_view"
                    android:layout_width="19dp"
                    android:layout_height="19dp"
                    android:contentDescription="@string/descr"
                    app:iiv_color="@color/dark_gray"
                    app:iiv_icon="faw-thumbs-o-up"
                    tools:src="@drawable/avatar_change_photo" />

                <TextView
                    android:id="@+id/view_on_likeTextView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_gravity="center_vertical"
                    android:layout_marginLeft="2dp"
                    android:gravity="center"
                    android:text="@string/like"
                    android:textColor="@android:color/tertiary_text_light"
                    android:textSize="12.0sp"
                    android:textStyle="bold"
                    tools:text="Like" />

            </LinearLayout>


            <LinearLayout
                android:id="@+id/item_view_on_map"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_toRightOf="@+id/item_view_favorite"
                android:background="?android:attr/selectableItemBackground"
                android:clickable="true"
                android:gravity="center"
                android:orientation="horizontal"
                android:padding="5dp">

                <com.mikepenz.iconics.view.IconicsImageView
                    android:layout_width="19dp"
                    android:layout_height="19dp"
                    android:layout_gravity="center_vertical"
                    android:contentDescription="@string/descr"
                    app:iiv_color="@color/dark_gray"
                    app:iiv_icon="faw-map-o"
                    tools:src="@drawable/avatar_change_photo" />

                <TextView
                    android:id="@+id/view_on_mapTextView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_gravity="center_vertical"
                    android:layout_marginLeft="4dp"
                    android:gravity="center"
                    android:text="@string/view_on_map"
                    android:textColor="@android:color/tertiary_text_light"
                    android:textSize="12.0sp"
                    android:textStyle="bold"
                    tools:text="View on map" />

            </LinearLayout>


            <LinearLayout
                android:id="@+id/item_view_share"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_alignParentEnd="false"
                android:layout_alignParentRight="false"
                android:layout_toRightOf="@+id/item_view_on_map"
                android:background="?android:attr/selectableItemBackground"
                android:clickable="true"
                android:gravity="center"
                android:orientation="horizontal"
                android:padding="5dp">

                <com.mikepenz.iconics.view.IconicsImageView
                    android:layout_width="19dp"
                    android:layout_height="19dp"
                    android:contentDescription="@string/descr"
                    app:iiv_color="@color/dark_gray"
                    app:iiv_icon="faw-share-square-o"
                    tools:src="@drawable/avatar_change_photo" />

                <TextView
                    android:id="@+id/item_shareTextView"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_marginLeft="4dp"
                    android:clickable="false"
                    android:ellipsize="end"
                    android:gravity="center_vertical"
                    android:lines="1"
                    android:text="@string/share"
                    android:textColor="@android:color/tertiary_text_light"
                    android:textSize="12.0sp"
                    android:textStyle="bold"
                    tools:text="Share" />

            </LinearLayout>

            <LinearLayout
                android:id="@+id/moreView"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_marginRight="2dp"
                android:layout_toRightOf="@+id/item_view_share"
                android:background="?android:attr/selectableItemBackground"
                android:clickable="true"
                android:padding="5dp">

                <com.mikepenz.iconics.view.IconicsImageView
                    android:layout_width="15dp"
                    android:layout_height="15dp"
                    android:layout_gravity="center"
                    android:contentDescription="@string/descr"
                    app:iiv_color="@color/dark_gray"
                    app:iiv_icon="gmd-more-vert" />

            </LinearLayout>

            <TextSwitcher
                android:id="@+id/item_image_likes"
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:layout_alignParentEnd="true"
                android:layout_gravity="center"
                android:gravity="center"
                android:inAnimation="@anim/slide_in_likes_counter"
                android:outAnimation="@anim/slide_out_likes_counter"
                android:paddingRight="16dp"
                tools:src="45 Likes">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:gravity="center" />

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:gravity="center" />
            </TextSwitcher>


        </RelativeLayout>


    </LinearLayout>


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

I'm facing the same problem.

All that I want to do is to set the image width to match the parent and change its height to keep its original aspect ratio.

I have an ImageView which is set with layout_width="match_parent" and layout_height="wrap_content".
My java code is like:

holder.ivMainImage.layout(0, 0, 0, 0);
Glide.with(mContext)
        .load(feedEntry.getMainImageUrl())
        .asBitmap()
        .fitCenter()
        .diskCacheStrategy(DiskCacheStrategy.RESULT)
        .into(holder.ivMainImage);

Is there any way to keep the ImageView's with the variable heights (according to image aspect ration) and prevent the annoying jumps?

@pedropalb I think the problem is that the items are reused and their size is calculated async. The ImageView can't measure itself properly while the bitmap is not available so it'll show up as size 0. As soon as the bitmap is loaded it will re-measure itself and the layout will change likely causing the jump.

If you know the image sizes before loading you can try staggered grid + percent layout as seen here: https://github.com/TWiStErRob/glide-support/tree/master/src/glide3/java/com/bumptech/glide/supportapp/github/_864_staggered_grid

Thank you @TWiStErRob!
I did it in a simpler way because I changed my requirements. I just constrained the aspect ratio to 16:9 according to the parent width. In other words, I set layout_width = match_parent and the height adjusts itself according to aspect ratio constraint.

I used AspectRatioImageView from twitter sdk (I was already using it in my app) like this:

import com.twitter.sdk.android.core.internal.util.AspectRatioImageView;
...
AspectRatioImageView ivMainImage = (AspectRatioImageView) view.findViewById(R.id.iv_main_image);
ivMainImage.setAspectRatio(1.78);
...

And the glide part I posted before changed to:

Glide.with(mContext)
                    .load(feedEntry.getMainImageUrl())
                    .asBitmap()
                    .centerCrop()
                    .diskCacheStrategy(DiskCacheStrategy.RESULT)
                    .into(holder.ivMainImage);

I removed the holder.ivMainImage.layout(0, 0, 0, 0). And I also changed fitCenter to centerCrop in order to fill the whole AspectRatioImageView without causing aspect ratio distortions.

I don't know if this is the most elegant way to do it. But it is working well.

@AngleV did you managed to figure this out?

This issue has been automatically marked as stale because it has not had activity in the last seven days. It will be closed if no further activity occurs within the next seven days. Thank you for your contributions.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kooeasy picture kooeasy  路  3Comments

billy2271 picture billy2271  路  3Comments

Morteza-Rastgoo picture Morteza-Rastgoo  路  3Comments

piedpiperlol picture piedpiperlol  路  3Comments

mttmllns picture mttmllns  路  3Comments