Search code examples
androidhierarchyviewer

ListView appearing only when attaching HierarchyViewer


I have a ListView with a custom Adapter that extends CursorAdapter. The list is defined in such a layout:

<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center">

        <ProgressBar
            android:id="@android:id/empty"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/ProgressBar.Itasa.Indeterminate.Large"/>

    <net.italiansubs.droitasa.widget.HookedListView
            android:id="@android:id/list"
            style="@style/NewsList"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone"/>

</RelativeLayout>

The HookedListView looks like this:

package net.italiansubs.droitasa.widget;

import android.content.Context;
import android.os.Handler;
import android.util.AttributeSet;
import android.widget.ListView;
import net.italiansubs.droitasa.util.OnLayoutChangedListener;

/**
 * An extended ListView that has an hook for getting layout changes.
 *
 * @author Sebastiano Poggi, Francesco Pontillo
 */
public class HookedListView extends ListView {

    private OnLayoutChangedListener mLayoutListener;
    private final Handler mHandler;
    private boolean mFirstLayout = true;

    public HookedListView(Context context) {
        super(context);
        mHandler = new Handler(context.getMainLooper());
    }

    public HookedListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mHandler = new Handler(context.getMainLooper());
    }

    public HookedListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mHandler = new Handler(context.getMainLooper());
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        if (mFirstLayout) {
            // In this case we want to avoid posting, because this could
            // have the two layout passes happen one after the other with
            // a pause between each other, with the effects, for example,
            // of having the number of columns change
            mFirstLayout = false;

            new LayoutChangedRunnable().run();
        }
        else if (changed) {
            mHandler.postAtFrontOfQueue(new LayoutChangedRunnable());
        }
    }

    /**
     * Sets the OnLayoutChangedListener for this instance, that will
     * receive callbacks after layout changes for the View.
     *
     * @param l The new OnLayoutChangedListener, or null to remove
     *          the current listener.
     */
    public void setOnLayoutChangedListener(OnLayoutChangedListener l) {
        mLayoutListener = l;
    }

    /**
     * Gets the OnLayoutChangedListener set for this instance.
     *
     * @return Returns the instance's OnLayoutChangedListener, if any.
     */
    public OnLayoutChangedListener getOnLayoutChangedListener() {
        return mLayoutListener;
    }

    private class LayoutChangedRunnable implements Runnable {

        @Override
        public void run() {
            // We don't need to synchronize on the listener because the handler
            // is hooked up to the main thread's looper so we shouldn't be
            // called from other threads.

            if (mLayoutListener != null) {
                mLayoutListener.onLayoutChanged(HookedListView.this);
            }
        }
    }
}

The problem I'm having is that the ListView contents don't appear on the device, even though the Cursor has items, the Adapter has items, and the ListView itself has items. Attaching the HierarchyViewer to the window does make the ListView items appear without any further intervention, and the same does requesting an update of the data from the webservice, which in turn updates the contents of the ContentProvider. I've tried setting up content observers for both the Cursor and the Adapter, and logging to ensure the correct number of items is set to the Adapter, and it's all working as intended. We already perform one extra layouting round on the List because of our custom MultiCoumnAdapter, that calculates the number of columns after the first ListView layout pass (thus the need for a HookedListView).

I've tried using a standard CursorAdapter, instead of my own MultiColumnAdapter (which emulates a GridView, without all its limitations and bugs), but this doesn't change anything.

The Fragment is the only content of an Activity; the Activity itself basically does nothing but getting an item ID from the extras of the Intent used to launch it, and passes it to the Fragment, which is defined in the Activity's layout XML as the only element.

What the Fragment does is just handling getting the data from the app's ContentProvider using a standard CursorLoader. We use DataDroid to manage getting new data from the webservice.

Is there anyone with the slightest clue on what's happening here? Seems like something gets stuck in the ListView own code until we update the underlying Cursor...


Solution

  • The fact you're messing with onLayout on ListView rings alarm bells. Why don't you just use a standard ListView with a OnGlobalLayoutListener?

    Or if you need the custom ListView, try changing your onLayout() to:

    final LayoutChangedRunnable mLayoutRunnable = new LayoutChangedRunnable()
    
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
    
        if (changed) {
            post(mLayoutRunnable);
        }
    }