-2

The problem I have is that listView.getLastVisiblePosition always returns -1 so I can't hide the searchView. I check this right after setting the adapter and anywhere I have tried to put this it still returns -1. I didn't see in the Docs why this would be but I imagine it would return -1 if the ListView is not showing any items. However, listView.getFirstVisiblePosition() returns 0 always, even when there is more than one item showing.

I have tried both methods Here but it doesn't make a difference when getting the wrong value.

@SuppressLint("NewApi") private void setFilters(String curType, Object curFilter)
{
    // initialize several lists
    itemsAdapter = new ArrayAdapter<Rowdata>(this, R.layout.list_item_text, foodItems);
    listView.setAdapter(itemsAdapter);

    int numItems = listView.getLastVisiblePosition() - listView.getFirstVisiblePosition();
    if (numItems > foodItems.length)    
    {   searchField.setVisibility(View.GONE);   }
    else
    {   searchField.setVisibility(View.VISIBLE);    }
}

This method is called any time a Button is pressed or text is changed that can filter through the list. So the question is why would listView.getLastVisiblePosition() always return -1 and why would listView.getFirstVisiblePosition() always return 0? No errors/exceptions, everything runs fine except for not getting the expected results. Note: itemsAdapter.getCount() returns the correct value.

Also, I have to support API >=10

Edit

If anyone needs clarification, let me know. But basically, I have an EditText I use to search through the list. I want to hide this when there aren't more items in the list than what fit on the screen. listView.getLastVisiblePosition() always returns -1

I would really like to know the cause of the original problem but if anyone has any better way of hiding the search box when items all fit on the screen, I am open to suggestions.

Update

I put a breakpoint in onItemClick() and there I get the correct values for getFirstVisiblePosition(), getLastVisiblePosition(), and listView.getChildCount(). Before this, I get 0, -1, and null respectively.

Community
  • 1
  • 1
codeMagic
  • 44,549
  • 13
  • 77
  • 93
  • I don't really know do I understand the question fully. You have a list view with an items count that you can get by getCount(). And inside this listView you have a searchView that you want to get yes? Does the searchView have any id assigned to it? – Mateusz Zając Jul 30 '13 at 17:18
  • @MateuszZając I expect `listView.getLastVisiblePosition()` to return the index in the `adapter` of the last `View` shown in the `ListView` so if it is less than or equal to the list size I don't show the search bar. I hope that cleared it up some. – codeMagic Jul 30 '13 at 17:21
  • It's not known before it's drawn. Try posting a runnable to via `listView.post()` and see if you get the right value there. – zapl Aug 02 '13 at 01:06
  • Just a [reference](http://stackoverflow.com/a/6853726/1050058) – Trung Nguyen Aug 02 '13 at 02:29
  • @zapl Thanks, that did it. Now if someone could explain to me WHY this works I could issue a bounty. Maybe I don't understand [Runnable](http://developer.android.com/reference/java/lang/Runnable.html) as well as I thought. But I don't understand why it knows the count here but not without a `Runnable`. If its drawn here then shouldn't it be drawn without the `Runnable? – codeMagic Aug 02 '13 at 13:10
  • @Yul thanks for the link. I swear I searched that exact title on SO and The Google and couldn't find anything. :\ – codeMagic Aug 02 '13 at 13:11

4 Answers4

3

What you need to do is roughly

listview.post(new Runnable() {
    public void run() {
        listview.getLastVisiblePosition();
    }
});

Why this way and not directly?

Android apps run in a big event loop known as the UI / main thread. Everything that is executed in there is the result of some event. For example when your Activity needs to be created that's some sort of Activity creation event. The loop will execute code that handles this event and will for example once your are considered "created" call the onCreate method. It might call more than one method within the same iteration but that's not really important.

When you setup things like the UI in any of those onSomething methods nothing is actually drawn directly. All you do is set some state variables like a new Adapter. Once you return from those on methods the system gains back control and will check what it needs to do next.

The system will for example check if the window needs to be redrawn and if so will enqueue a redraw event in the event queue which is at a later point executed by the loop. If nothing needs to be drawn it's just idle and will wait for example for touch events that are enqueued for that loop as well.

Back to your problem: By calling .setAdapter() you essentially reset all states of the ListView. And since actual updates of the ListView will only happen after you hand control back to the system you will get nothing useful out of .getLastVisiblePosition().

What needs to happen before is that ListView is instructed to be redrawn or to measure it's new size, count the amount of items it has and so on. Once it has done that it will be able to give you the required information.

.post(Runnable r) simply enqueues a Runnable into the eventqueue which is then executed by the loop once it's first in the queue.

a Runnable does not require a Thread, it's just a regular Object with a method named run() and the contract of a Runnable is simply that something (which often happens to be a Thread) can call the run() method to execute whatever you want to run. Magical loop does that.

Result of you posting a runnable is looks inn pseudo code somewhat like this:

void loop() {
    yourActivity.onSomething() { loop.enqueue(runnable) }
    ListView.redraw()            //       |
    runnable.run()               //    <--+
}
zapl
  • 63,179
  • 10
  • 123
  • 154
0

My suggestion to resolve this problem will not be professional or light weight.

I am suggesting that you should get count of all views in listView and check every one of them are they visible.

example:

   private int getIndexOfLastVisibleView(ListView view){

      int count = view.getChildCount()-1;

      for(int i = count ; i>=0 ; i--){
         View checkedView = view.getChildAt(i);
         if(checkedView.isShown()){
            return i;
         }
      }


      return -1;
  }

May not be perfect but I hope that it will work.

Mateusz Zając
  • 316
  • 5
  • 19
  • Thanks for your response. I would like something a little tidier if I can but I may try this if I don't figure it out. Also, I'm really interested in knowing why `getLastVisiblePostition()` is returning -1 every time because that is the heart of the issue – codeMagic Jul 30 '13 at 17:52
  • I'm sorry but I can't help you here. I never used this method in any way. – Mateusz Zając Jul 30 '13 at 20:56
  • I appreciate the answer anyway. If I don't find what I'm looking for soon then I will try it and let you know. Thanks! – codeMagic Jul 30 '13 at 20:57
  • I was prepared to at least upvote for now but this isn't working either. If you see my update you will see why. – codeMagic Jul 31 '13 at 19:40
0

You can refer to my answer here Strange problem with broadcast receiver in Android not exactly the same but you can get the idea why your code not working.

To make it more clear, when you set the adapter to the ListView, nothing has been drawn yet and the method getLastVisiblePosition() can only return the correct value after the listview finish drawing all of it's visible children and know which one is the last visible one.

So, the most appropriate approach I can suggest here is trigger a callback after the listView finished drawing and we get the correct value then.

The ListView with listener after drawing:

static class MyListView extends ListView {
        private OnDrawCompletedListener mOnDrawCompletedListener;

        public MyListView(Context context) {
            super(context);
        }

        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            if (mOnDrawCompletedListener != null) {
                mOnDrawCompletedListener.onDrawCompleted();
            }
        }

        public void setOnDrawCompletedListener(OnDrawCompletedListener listener) {
            mOnDrawCompletedListener = listener;
        }

        public static interface OnDrawCompletedListener {
            public void onDrawCompleted();
        }
    }

The sample code for getting last visible position

mListView.setAdapter(new EfficientAdapter(this));
//Will get -1 here
Log.e("Question-17953268",
        "getLastVisiblePosition  = "
                + mListView.getLastVisiblePosition());

mListView.setOnDrawCompletedListener(new OnDrawCompletedListener() {

    @Override
    public void onDrawCompleted() {
        //Will get correct value here
        Log.e("Question-17953268",
                "getLastVisiblePosition  = "
                        + mListView.getLastVisiblePosition());
    }

});
Community
  • 1
  • 1
Binh Tran
  • 2,478
  • 1
  • 21
  • 18
0

Thanks to zapl's answer I was able to get what I needed. I thought I would post the full code in case it helps anyone

listView.post(new Runnable()
{       
    public void run()
    {
        int numItemsVisible = listView.getLastVisiblePosition() - 
                listView.getFirstVisiblePosition();
        if (itemsAdapter.getCount() - 1 > numItemsVisible)
        {   searchField.setVisibility(View.VISIBLE);    }                                   
        else
        {   
            searchField.setVisibility(View.GONE);   
            setFilters("searchtext", "");
        }               
    }
});
codeMagic
  • 44,549
  • 13
  • 77
  • 93