Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - delayed clicks in ListView

I have the following structure in my app:

FragmentActivity with ViewPager holding multiple fragments managed by FragmentStatePagerAdapter using compativility pack with Android 2.1

Each fragment contains ListView. Each element in the ListView has a LinearLayout with two TextViews and a Button. The LinearLayout and the button have onClickListeners (separate). Clicking on the LinearLayout starts another Activity. I noticed that the clicks behavior is very inconsistent: sometimes action is executed immediately but very often it is delayed and sometimes it's just ignored no matter how many times I tap. It gets even weirder because I can tap and the action will only be executed when I start scrolling the list. I tried various combinations of setFocusable(false) and setSelectable(true) but it seems to not make any difference. Any ideas? I'll be happy to provide more details.

like image 597
Bostone Avatar asked Jan 11 '12 06:01

Bostone


3 Answers

I ran into same problem, but in my case the solution was not to keep the references to views, which caused problems with ListView's view caching. After properly implementing getView() method with use of converView, all strange behaviour with lost / unexpected click calls was gone.

like image 45
Fenix Voltres Avatar answered Nov 20 '22 06:11

Fenix Voltres


I had a similar problem and it took me 2 days to debug and solve it. I have a ListAdapter which creates several TextViews in a LinearLayout for every list item. Each TextView has it's own OnClickListener, because I need to handle clicks on each item.

When I changed the implementation so that I reuse the Views the OnClickListener stopped working correctly. On 4.4.2 most clicks worked but sometimes there was no reaction until I scrolled in the list. On 2.3 the first clicks would not work and then all clicks where handled in a burst.

In my special case I created all the View in Java code and not by inflating resources. And the critical point was, that I did set the LayoutParams of the LinearLayout even when the view was reused (this seems to be more safe, then assuming that the reused view has the correct layout parameters). When I don't set the LayoutParams when reusing everything works fine! Here is the critical code:

public View getView(int position, View convertView, ViewGroup parent) {
    LinearLayout tapeLine = null;
    if (convertView != null && convertView instanceof LinearLayout && ((LinearLayout)convertView).getChildCount() == 4) tapeLine = (LinearLayout) convertView; // Reuse view
    else tapeLine = new LinearLayout(activity);
    if (convertView == null) { // Don't set LayoutParams when reusing view
        ViewGroup.LayoutParams tapeLineLayoutParams = new AbsListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        tapeLine.setLayoutParams(tapeLineLayoutParams);
    }
    ScrollingTape scrollingTape = calculatorHolder.getCalculator().getScrollingTape();
    int tapeWidthPx = parent.getWidth();
    TapeLineTextSizeInfo tapeLineTextSizeInfo = calculatorHolder.getTapeLineTextSizeHelper().createTapeLineTextSizeInfo(tapeWidthPx);
    ScrollingTapeLine line = scrollingTape.getLine(position);
    tapeLine.setOrientation(LinearLayout.HORIZONTAL);
    int tapeBackgroundColor = getBackgroundColor(line);
    tapeLine.setBackgroundColor(tapeBackgroundColor);
    addColumnViews(tapeLine, line, tapeLineTextSizeInfo);
    tapeLine.setTag(R.id.scrollingtapeadapter_viewtag_position, position);
    tapeLine.setOnLongClickListener(longClickListener);
    tapeLine.setOnClickListener(remainClickListener);
    return tapeLine;
}

What is the background for this strange behaviour of the list view? I did a bit of debugging and research in the Android sources. When android updates a view there are two important steps onMeasure and onLayout. The getView method of the ListAdapter is not only called to draw the view but also earlier during onMeasure. In this later case the view is created but it is not yet registered in the event chain to handle click events.

When a view which has been created for onMeasure is reused later to be acutally drawn to the screen, it must be register from the Android system to handle click events. For this special case the Android develepers had done something, which could be considered as a dirty hack. A special flag in the LayoutParams is used to decide that the view has to be registered in the event change.

Now my problem: by resetting the LayoutParams also when a view is reused, this flag was always reset. Therefore the Android system would not register the view and events would not come through.

to summarize: when resusing a view in getView of a ListAdapter don't overwrite the LayoutParams because they keep internal information of the android system.

like image 50
user3599248 Avatar answered Nov 20 '22 08:11

user3599248


In case anybody wondered how I solved this. Basically I had to simplify my layouts. It seems that when you have complex nested structures events can take too long to bubble and if you start scrolling list at the same time event can trigger wrong action. I trimmed the layouts by switching to RelativeLayout as much as possible and that seemed to help a lot

like image 3
Bostone Avatar answered Nov 20 '22 08:11

Bostone