Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OnGlobalLayoutListener in ListView

I want to be able to expand the rows in my ListView with an animation. Therefore I need to know the height of the view that is expanding. I'm using this in the getView() method of my ArrayAdapter:

mDetailsView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

    @Override
    public void onGlobalLayout() {
        mDetailsView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

        onClickListener.setHeight(mDetailsView.getHeight());
        mDetailsView.setVisibility(View.GONE);
    }
});

This works fine for the rows that are visible on screen at start, but when I scroll down, the onGlobalLayout method doesn't get called for the rows that weren't visible at first.

How can I get the height for those rows?

like image 649
nhaarman Avatar asked Jan 14 '13 19:01

nhaarman


2 Answers

It is possible that implementations of ListView don't do an entire layout so the ViewTreeObserver never actually sees a layout in progress.

Unless there's some specific phone case I'm not aware of, you can use the post method in Views to execute runnable when they're in view.

mDetailsView.post(new Runnable() {
  @Override
  public void run() {
    onClickListener.setHeight(mDetailsView.getHeight());
    mDetailsView.setVisibility(View.GONE);
  }
});

EDIT:

I don't know how the entire getView() method is laid out. The issue, if I had to guess, is the ListView simply isn't requesting a layout. Instead it's doing the work itself for each view to speed things up. What you can try is this:

public void getView(int position, View convertView, ViewGroup parent) {

  /* 
   * set your view up
   */

  mDetailsView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

    @Override
    public void onGlobalLayout() {
        mDetailsView.getViewTreeObserver().removeGlobalOnLayoutListener(this);

        onClickListener.setHeight(mDetailsView.getHeight());
        mDetailsView.setVisibility(View.GONE);
       }
   });

   notifyDataSetInvalidated(); // Notify the ListView() and any other listeners that your views are invalid.
   return view;
}

New edit: Using notifyDataSetInvalidated() in usually a bad idea and especially if used in getView().

To pre-measure the layout you would take it's layout params or give it new ones if they don't exist.

    LayoutParams params = newView.getLayoutParams();
    if (params == null) {
        params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
    }
    final int widthSpec = MeasureSpec.makeMeasureSpec(parent.getWidth(), MeasureSpec.UNSPECIFIED);
    final int heightSpec = MeasureSpec.makeMeasureSpec(parent.getHeight(), MeasureSpec.UNSPECIFIED);
    newView.measure(widthSpec, heightSpec);
like image 199
DeeV Avatar answered Sep 20 '22 13:09

DeeV


In ListView or RecyclerView instead of using OnGlobalLayoutListener I always use OnPreDrawListener. This callback is fired also for non visible rows at start. From the official documentation:

At this point, all views in the tree have been measured and given a frame.

like image 33
Bystysz Avatar answered Sep 16 '22 13:09

Bystysz