2

Ok, I have the following requirements:

  • An EditText in which if the user types @ a popup with suggestions appears
  • The user can continue typing which will filter the suggestions
  • The user can tap on a suggestion which will complete the entry and dismiss the popup
  • The user can tap outside the popup which will dismiss it

Here is my code:

        PopupWindow popupWindow = new PopupWindow(mContext);

        // Create ListView to show the suggestions
        ListView listView = new ListView(mContext);
        listView.setBackgroundColor(Color.WHITE);
        MentionsAdapter adapter = new MentionsAdapter(mContext, suggestions);
        listView.setAdapter(adapter);
        listView.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();
            }
        });
        mSuggestionsListView = listView;

        popupWindow.setContentView(listView);
        popupWindow.setFocusable(false);
        popupWindow.setTouchable(true);
        popupWindow.setOutsideTouchable(true);

        popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {

            @Override
            public void onDismiss() {
                clearPopupData();
            }
        });

        Rect popupRect = calculatePopupPosition();
        popupWindow.setWidth(popupRect.width());
        popupWindow.setHeight(popupRect.height());
        popupWindow.showAtLocation(mEditor, Gravity.START | Gravity.TOP, popupRect.left, popupRect.top);

        mPopupWindow = popupWindow;

It does work! The problem is that it works only on some Android versions/devices. For example it works on Android 6.0 on the emulator but it doesn't work on my colleague's LG G4 with 6.0. It doesn't work on Android 4.3.1 but works on 4.4.2. If the PopupWindow is not focusable the ListView's OnItemClickListener is not called. If the PopupWindow is focusable the ListView's OnItemClickListener is called but EditText doesn't receive keyboard events. I tried countless combinations of changing focus/touch modes on the PopupWindow and ListView and couldn't get it working in those cases.

Any suggestions on WFT is going on and how to fix it?

Jean Vitor
  • 893
  • 1
  • 18
  • 24
AXE
  • 8,335
  • 6
  • 25
  • 32

2 Answers2

1

I found a way to achieve what I want. As it is more a hack than a real answer to why this inconsistent behavior is happening I will not accept it as an answer. Still, it may be valuable to people looking to achieve the same requirements.

What I did was surrender and make PopupWindow focusable in order to enable it's embedded ListView to receive touch events:

popupWindow.setFocusable(true);

As this effectively makes the ListView also the receiver of keyboard events I made an OnKeyListener that forwards the events to the EditText:

        listView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                mEditor.dispatchKeyEvent(event);
                return false;
            }
        });

For clearance here is the complete modified code from the question:

        PopupWindow popupWindow = new PopupWindow(mContext);

        // Create ListView to show the suggestions
        ListView listView = new ListView(mContext);
        listView.setBackgroundColor(Color.WHITE);
        listView.setOnKeyListener(new View.OnKeyListener() {
            @Override
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                mEditor.dispatchKeyEvent(event);
                return false;
            }
        });
        MentionsAdapter adapter = new MentionsAdapter(mContext, suggestions);
        listView.setAdapter(adapter);
        listView.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();
            }
        });
        mSuggestionsListView = listView;

        popupWindow.setContentView(listView);
        popupWindow.setFocusable(true);
        popupWindow.setTouchable(true);
        popupWindow.setOutsideTouchable(true);

        popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {

            @Override
            public void onDismiss() {
                clearPopupData();
            }
        });

        Rect popupRect = calculatePopupPosition();
        popupWindow.setWidth(popupRect.width());
        popupWindow.setHeight(popupRect.height());
        popupWindow.showAtLocation(mEditor, Gravity.START | Gravity.TOP, popupRect.left, popupRect.top);

This seems to work on all Android versions/devices. If visual focusing of the items in the ListView is not desired you can set the background of the ListView's items to a color or a drawable that doesn't change when focused.

AXE
  • 8,335
  • 6
  • 25
  • 32
0

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;
    }
Edijae Crusar
  • 3,473
  • 3
  • 37
  • 74