13

I am having trouble with one of my adapters. I have a selection of images which I display in a grid like the following:

enter image description here

This works well when I have a single screen worth of items and they all show up fine. The problem appears when there are more items to scroll down to. Initially when I scroll down further the items appear blank, the GridLayout containers are there and the code for displaying the images gets run, but they do not appear on the screen as shown here:

The problem

If I scroll back up, then scroll back down again, the images appear exactly where they should be:

enter image description here

It seems like I need to refresh the layout or tell the renderer that something has changed, but nothing I try seems to do anything. I have tried calling requestLayout and invalidate but it doesn't seem to solve the issue. I also registered a GlobalOnLayoutListener, but that didn't fare any better.

Here is the relevant code:

This is from my onBindViewHolder method

final GridLayout grid = ((KKAlbumViewHolder) holder).mGridLayout;
grid.removeAllViews();
int width = grid.getWidth();
if(width > 0) {
    albumHolder.setPreviewImages(posts);
}else {
    albumHolder.itemView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    albumHolder.itemView.removeOnLayoutChangeListener(this);
                    albumHolder.setPreviewImages(posts);
                    albumHolder.mGridLayout.invalidate();
                }
    });
}

This is the method I use to layout the images. It basically lays the images out in an irregular grid, so if there is a long or tall image, that image will attempt to take up a full row or a full column depending on what space is free. The reason i set the param width and height is so picasso doesn't complain about the .fit() method:

public void setPreviewImages(ArrayList<KKPost> posts) {
        Picasso picasso = Picasso.with(itemView.getContext());

        int space = 0;
        int tl = 1 << 0;
        int tr = 1 << 1;
        int bl = 1 << 2;
        int br = 1 << 3;
        int lCol = tl | bl;
        int rCol = tr | br;
        int tRow = tl | tr;
        int bRow = bl | br;
        int full = lCol | rCol;
        boolean hasLongImage = false;
        for(KKPost post : posts) {
            if(space == full){
                break;
            }

            int margin = 2;

            ImageView view = new ImageView(itemView.getContext());
            GridLayout.LayoutParams param = new GridLayout.LayoutParams();
            view.setBackgroundColor(Color.LTGRAY);
            if(posts.size() < 4){
                param.columnSpec = GridLayout.spec(0, 2);
                param.rowSpec = GridLayout.spec(0, 2);
                param.width = mGridLayout.getWidth();
                param.height = mGridLayout.getHeight();
                mGridLayout.addView(view, param);
                picasso.load(post.url).fit().centerCrop().into(view);
                break;
            }

            if (post.aspectRatio >= 1.2 && !hasLongImage) {
                //landscape
                if((space & tRow) == 0){
                    param.rowSpec = GridLayout.spec(0, 1);
                    param.columnSpec = GridLayout.spec(0, 2);
                    param.height = mGridLayout.getHeight()/2;
                    param.width = mGridLayout.getWidth();
                    param.bottomMargin = margin;
                    space |= tRow;
                    mGridLayout.addView(view, param);
                    picasso.load(post.url).fit().centerCrop().into(view);
                    hasLongImage = true;
                    continue;
                }
                if((space & bRow) == 0){
                    param.rowSpec = GridLayout.spec(1, 1);
                    param.columnSpec = GridLayout.spec(0, 2);
                    param.height = mGridLayout.getHeight()/2;
                    param.width = mGridLayout.getWidth();
                    param.topMargin = margin;
                    space |= bRow;
                    mGridLayout.addView(view, param);
                    picasso.load(post.url).fit().centerCrop().into(view);
                    hasLongImage = true;
                    continue;
                }
            } else if (post.aspectRatio <= 0.8 && post.aspectRatio > 0 && !hasLongImage) {
                //portrait
                if((space & lCol) == 0){
                    param.columnSpec = GridLayout.spec(0, 1);
                    param.rowSpec = GridLayout.spec(0, 2);
                    param.height = mGridLayout.getHeight();
                    param.width = mGridLayout.getWidth()/2;
                    param.rightMargin = margin;
                    space |=  lCol;
                    mGridLayout.addView(view, param);
                    picasso.load(post.url).fit().centerCrop().into(view);
                    hasLongImage = true;
                    continue;
                }
                if((space & rCol) == 0){
                    param.columnSpec = GridLayout.spec(1, 1);
                    param.rowSpec = GridLayout.spec(0, 2);
                    param.height = mGridLayout.getHeight();
                    param.width = mGridLayout.getWidth()/2;
                    param.leftMargin = margin;
                    space |= rCol;
                    mGridLayout.addView(view, param);
                    picasso.load(post.url).fit().centerCrop().into(view);
                    hasLongImage = true;
                    continue;
                }
            }
            //square or no space or unknown
            if((space & tl) == 0){
                param.columnSpec = GridLayout.spec(0,1);
                param.rowSpec = GridLayout.spec(0,1);
                space |= tl;
                param.bottomMargin = margin;
                param.rightMargin = margin;
            }else
            if((space & tr) == 0) {
                param.columnSpec = GridLayout.spec(1,1);
                param.rowSpec = GridLayout.spec(0,1);
                space |= tr;
                param.bottomMargin = margin;
                param.leftMargin = margin;
            }else
            if((space & bl) == 0){
                param.columnSpec = GridLayout.spec(0,1);
                param.rowSpec = GridLayout.spec(1,1);
                space |= bl;
                param.rightMargin = margin;
                param.topMargin = margin;
            }else
            if((space & br) == 0){
                param.columnSpec = GridLayout.spec(1,1);
                param.rowSpec = GridLayout.spec(1,1);
                space |= br;
                param.leftMargin = margin;
                param.topMargin = margin;
            }
            param.height = mGridLayout.getHeight()/2;
            param.width = mGridLayout.getWidth()/2;
            mGridLayout.addView(view, param);
            picasso.load(post.url).fit().centerCrop().into(view);
        }
    }
}

Why does this seem to skip out on the first rendering pass for the items not visible initially?

EDIT: I did a bit of refactoring so I could re-use imageviews instead of removing/creating new ones and have found out that the views being added for those items dont get an onLayoutChange callback. All of the other items get the onLayoutChange callback, and as before, the items that don't get the call initially, get it after I scroll up and scroll down again. See:

view.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
                @Override
                public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                    view.removeOnLayoutChangeListener(this);
                    picasso.load(post.url).into(view);
                }
            });

Edit: Nov. 15

Here is the xml for the viewholder layout

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    android:id="@+id/album_card_view"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    card_view:cardCornerRadius="@dimen/feed_card_corner_radius"
    card_view:cardElevation="@dimen/cardview_default_elevation"
    card_view:cardMaxElevation="@dimen/cardview_default_elevation"
    card_view:cardUseCompatPadding="true"
    xmlns:autofit="http://schemas.android.com/apk/res-auto"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <android.support.percent.PercentRelativeLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/percent_layout">

            <GridLayout
                android:background="@color/white"
                android:id="@+id/preview_grid_layout"
                android:columnCount="2"
                android:rowCount="2"
                android:alignmentMode="alignBounds"
                android:layout_height="0dp"
                android:layout_width="0dp"
                app:layout_widthPercent="100%"
                app:layout_aspectRatio="100%">

            </GridLayout>

        </android.support.percent.PercentRelativeLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="@dimen/card_padding_4dp"
            android:orientation="vertical">

            <TextView
                android:id="@+id/album_title"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                tools:text="Family Photos"/>

         </LinearLayout>

    </LinearLayout>

</android.support.v7.widget.CardView>
Nic Robertson
  • 1,198
  • 9
  • 26
  • If anyone can think of a better title for this question feel free to edit it – Nic Robertson Nov 07 '16 at 03:12
  • Every time `onBindViewHolder` is called you are replacing all the images from the `GridView`? Why? – rubenlop88 Nov 07 '16 at 03:22
  • Well there can be anywhere from 1-4 images in the layout for any single item, so when the items got recycled it would lead to some overhang. I guess I could keep a list of ImageViews and then just set them to visibility gone when they get recycled, but the performance of the scrolling was decent enough, so I was less bothered about that than the rendering issue – Nic Robertson Nov 07 '16 at 03:28
  • What version of support library are you using? If you're on the latest (25.0.0) can you switch back to the previous one and check if it has the same issue? I had a similar problem few days ago, and I'd like to get this feedback before answering. – Mimmo Grottoli Nov 09 '16 at 18:23
  • @MimmoGrottoli My support version is 24.2.1 at the moment I am targeting api level 24 – Nic Robertson Nov 09 '16 at 20:05
  • Can you try calling the 'setPreviewImages(ArrayList posts)' method within your adapter 'onViewAttachedToWindow' callback, that you should override? Let me know! – Mimmo Grottoli Nov 09 '16 at 20:44
  • @MimmoGrottoli `onViewAttachedToWindow` has no reference to the item position, which I need so that I can get the `ArrayList` to pass into the preview images method – Nic Robertson Nov 09 '16 at 20:52
  • Yes, you pass the data in your 'onBindViewHolder'. The onViewAttachedToWindow is called after that, so you could apply the Picasso image loading. – Mimmo Grottoli Nov 09 '16 at 20:55
  • @MimmoGrottoli no change in behaviour – Nic Robertson Nov 09 '16 at 21:10
  • Are you using item decoration? if you using itemdecoration means, remove that and check whether its loading or not. – Stephen Nov 11 '16 at 09:50
  • @Stephen There are no item decorations on the reyclerview. And it seems that it is only the image views skipping the onLayout call. The gridlayout shows as the correct size in the uiautomatorviewer – Nic Robertson Nov 11 '16 at 18:58
  • are you using nested scroll view? if you are using remove that and check. – Stephen Nov 14 '16 at 05:05
  • Post you layout .xml file which contains recyclerview – Beena Nov 14 '16 at 12:00
  • Why are you calling 'itemView.addOnLayoutChangeListener'? If it's for a reason I'm missing, why not call that within the holder, after you have made all the changes you wanted to at first? Also, are you doing anything in the ViewHolder constructor that should probably be done in a function? – Pablo Baxter Nov 16 '16 at 00:14
  • @PabloBaxter I added it to the itemView simply because it is a view that all ViewHolders by default should have, so when it the layout is changed, I know that the view has a non-zero width/height. I previously had it as itemview.mGridLayout which had no change in effect. There is nothing in the constructor apart from super calls and Butterknife view injection – Nic Robertson Nov 16 '16 at 01:44

3 Answers3

0

As you are trying to make a list perhaps you should consider using the RecyclerView class. This way you will be able to utilize the LayoutManager to layout your pictures more easily. This could be better as your layout is not a standard grid (which has constant columns widths and heights). An interesting example to look at for a custom LayoutManager can be found here.

Doron Yakovlev Golani
  • 5,188
  • 9
  • 36
  • 60
  • This is using a RecyclerView. Each of the ViewHolders has a GridLayout inside it – Nic Robertson Nov 15 '16 at 22:29
  • GridLayout inside a RecyclerView seems a bit like an overkill. You can have multiple elements of different sizes in a single line when using a RecyclerView with a custom layout manager. If I were you, I'd start by trying to make things simpler (either getting rid of the GridView and replacing it with something simpler or using only the GridView without the complex layout around it) just for testing purposes. Another option is to replace Picasso with Glide (again for elimination purposes). – Doron Yakovlev Golani Nov 16 '16 at 03:58
0

Since complete code is not there so I can make a guess. It seems when you scroll first time recycler adapter creates the views before grid gets a chance to create and add views. I guess if you use another adapter i.e. grid adapter to populate your grid views, it should work fine.

I am hopping you are using onScrollListener for your recycler view to detect scrolling and then load more grids which in turn load more grid items through grid adapter for each and every grid.

Dalvinder Singh
  • 2,129
  • 4
  • 21
  • 20
-1

Check whether you have setHasStableIds(true), if you do that you have to override getItemId(int position) to specify ids for each views.If you dont do that The subsequent onBindViewholders() calls will not triggered and your view will not be updated

NIPHIN
  • 1,071
  • 1
  • 8
  • 16
  • `setStableIds()` is not set to true. The `onBindViewHolder` method is called correctly like it states in my question, but the image views do not get the onLayout calls. The other info on the viewholder is correct. – Nic Robertson Nov 14 '16 at 20:25