Android ListView's OnItemClickListener not working on non-focusable PopupWindow

1.1k views Asked by At

In my Android app I have an EditText. When the EditText is focused, a PopupWindow is shown, which contains a ListView. The user can continue typing in the EditText when the PopupWindow is on screen, and in the mean time he/she should be able to click an item on the ListView to dismiss the PopupWindow. (Depending on which ListView item is clicked, in addition to dismissing the PopupWindow something else should also happen, but it's irrelevant here.)

The problem is, although the "continue typing" part works, the "click an item" part doesn't, because the OnItemClickListener of the ListView never gets called, and I can't figure out why.

Here's my activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ddd"
    android:focusableInTouchMode="true"
    tools:context="com.example.popuptest3.MainActivity" >

    <EditText
        android:id="@+id/edtPhone"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="phone" />

</RelativeLayout>

My popup_window.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#fff" >

    <ListView
        android:id="@+id/lstItems"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="60dp" />

    <Button
        android:id="@+id/btnDismiss"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="Dismiss" />

</RelativeLayout>

My list_item.xml:

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

    <TextView
        android:id="@+id/txtItem"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:text="Item Text"
        android:textAppearance="?android:attr/textAppearanceMedium" />

</RelativeLayout>

In my MainActivity class I have the following preparation code:

private PopupWindow popup = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    prepareControls();
}

private void prepareControls() {
    final EditText edtPhone = (EditText)findViewById(R.id.edtPhone);
    edtPhone.setOnFocusChangeListener(new OnFocusChangeListener() {
        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if (hasFocus)
                openPopup(edtPhone);
            else
                closePopup();
        }
    });
}

That is, I open the PopupWindow when edtPhone gets focus, and close the PopupWindow when edtPhone loses focus. Then I have:

private void openPopup(View parent) {
    closePopup();

    // Inflate the view of the PopupWindow.
    LayoutInflater layoutInflater = LayoutInflater.from(this);
    View view = layoutInflater.inflate(R.layout.popup_window,
            new LinearLayout(this), false);
    final Activity activity = this;

    // Prepare the ListView.
    ListView lstItems = (ListView)view.findViewById(R.id.lstItems);
    lstItems.setAdapter(new ListAdapter(this));
    lstItems.setOnItemClickListener(new OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view,
                int position, long id) {
            hideKeyboardAndClearFocus(activity);
        }
    });

    // Prepare the Button.
    Button btnDismiss = (Button)view.findViewById(R.id.btnDismiss);
    btnDismiss.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            hideKeyboardAndClearFocus(activity);
        }
    });

    // Create and show the PopupWindow.
    popup = new PopupWindow(view, 400, 300, false);
    popup.showAtLocation(parent, Gravity.LEFT | Gravity.TOP, 0, 60);
}

The btnDismiss button is not part of my plan; I add it here just for demonstration purposes. Finally I have:

private void closePopup() {
    if (popup != null) {
        popup.dismiss();
        popup = null;
    }
}

private static void hideKeyboardAndClearFocus(Activity activity) {
    InputMethodManager manager = (InputMethodManager)activity.
            getSystemService(Activity.INPUT_METHOD_SERVICE);
    manager.hideSoftInputFromWindow(
            activity.getCurrentFocus().getWindowToken(), 0);
    activity.getWindow().getDecorView().clearFocus();
}

I think this doesn't require explanation, and the ListAdapter class is quite standard so I omit it.

Now, when I click on edtPhone, it gets focus, the soft keyboard shows up, and the PopupWindow opens. I can type something into edtPhone, and when I click on btnDismiss, edtPhone loses focus, the soft keyboard is dismissed, and the PopupWindow closes, all as expected. But if instead of btnDismiss I click on an item on lstItems, nothing happens. Using Log I can see that OnItemClickListener is not called. The funny thing is, the ListView can be scrolled well, it just cannot be clicked.

I know this post but that solution doesn't work for my purpose. If I change this line:

popup = new PopupWindow(view, 400, 300, false);

to the following:

popup = new PopupWindow(view, 400, 300, true);

the PopupWindow becomes focusable and the ListView becomes clickable, alright, but then I cannot continue typing into edtPhone. In fact, if there are other controls on the activity, they all become unreachable, as if the PopupWindow is "modal".

I also tried android:focusable="false", no luck. And I'm not doing auto-complete so AutoCompleteTextView isn't what I need.

So why is OnItemClickListener not called on non-focusable PopupWindow? Am I doing anything wrong?

2

There are 2 answers

2
Shivakumar Ramachandhran On BEST ANSWER

set the popupwindow to non-focusable

popupwindow.setFocusable(false);
popupwindow.setTouchable(true);
popupwindow.setOutsideTouchable(true);

extend the ListView and override the following methods

@Override
public boolean hasFocus() {
    return true;
}

@Override
public boolean isFocused() {
    return true;
}

@Override
public boolean hasWindowFocus() {
    return true;
}

This is how AutoCompleteTextView implements the popup for suggestions. If you need to select the list item using DPAD keys or other navigation keys , then you need to pass the key events from the EditText to the ListView.

0
Edijae Crusar On

I solved this problem by just setting an OnClickListener on convertview, which is a parameter of adapter's getView method. I then calls onItemClick method of the listview's onItemClickListener.

No need to make popupWindow focusable. Infact, just using ListPopupWindow is easier since its only inside getView() adapter method that we are adding some few lines of code

ListPopupWindow popupWindow = new ListPopupWindow(mContext);

    MentionsAdapter adapter = new MentionsAdapter(mContext, suggestions);
    popupWindow.setOnItemClickListener(new android.widget.AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            String username = mSuggestions.get(position);
            String mentionCompletion = username.substring(mCurrentQuery.length()).concat(" ");
            mEditor.getText().insert(mEditor.getSelectionEnd(), mentionCompletion);
            hideSuggestions();
        }
    });
   popupWindow.setAdapter(adapter);

MentionsAdapter getView method

@Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if(convertView == null){
            convertView = LayoutInflater.from(context).inflate(,R.layout.item_user,parent,false);

        }
        // this is the key point of workaround
        convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               popupWindow.getListView().getOnItemClickListener()
                 .onItemClick(listView, v, position, getItemId(position));
            }
        });
        return convertView;
    }