Catching an event when spinner drop down is dismissed
Asked Answered
P

5

18

I want to catch an event when spinner drop down is dismissed. We can catch it when the user clicks on any item in the onItemSelected(). But I want to catch even when user touches outside of the drop down area or back button as these too make it disappear. In these two causes when I observed log, it says "Attempted to finish an input event, but the input event receiver has already been disposed"

I observed the source code, this is printed from the InputEventReceiver.java in the finishInputEvent(InputEvent event, boolean handled) method. But it's a final method, so there is no point of overriding it. Can some one please suggest the way to handle when the drop down is dismissed in those cases?

Prophet answered 7/11, 2013 at 10:2 Comment(5)
Did you check using setOnItemSelectedListener()?Diahann
@Diahann yes it's overriding onNothingSelectd() functionGaylenegayler
@Diahann Yes, but the control doesn't come to onNothingSelected() when it is dismissed.Prophet
@Appu Facing same problem. Did you find any solution?Threadfin
@SyedMuhammadUmair I totally forgot about this question, so didn't answer my question. I have self-answered now, but this would be useless to you if you strictly want to use Spinner. I have used Popup Menu.Prophet
P
1

I have used Popup Menu instead of Spinner. Because as far as my knowledge, dismiss event couldn't be caught with spinner, but with Popup menu I did it by setting onDismissListerner() to the popup menu

popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {  
             public boolean onMenuItemClick(MenuItem item) {  
              Toast.makeText(MyActivity.this,"Clicked on: " + item.getTitle(),Toast.LENGTH_LONG).show();  
              return true;  
             }  
            });  
popup.setOnDismissListener (new PopupMenu.OnDismissListener(){

public void onDismiss()
{
   //catch dismiss event here.
}
});
Prophet answered 20/2, 2014 at 9:17 Comment(2)
Thanks but that requires min API 14, and i need to support API 11.Threadfin
I ended up using ListPopupWindow (Added in API level 11)Threadfin
A
0

What about looking for another event like onDetachFromWindow? A spinner doesn't have any of the regular lifecycle events that we work with a lot -- it would be nice to have an onStop or onDestroy to work with. Of course, you would have to extend the spinner class and create an interface to define your own listener:

public class ChattySpinner extends Spinner {
    private ChattySpinnerListener chattyListener;

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

    public ChattySpinner(Context context, int mode) {
        super(context, mode);
    }

    public ChattySpinner(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public ChattySpinner(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public ChattySpinner(Context context, AttributeSet attrs, int defStyle, int mode) {
        super(context, attrs, defStyle, mode);
    }

    public void setChattyListener(ChattySpinnerListener listener) {
        this.chattyListener = listener;
    }

    @Override
    protected void onDetachedFromWindow() {
        if(chattyListener != null) {
            chattyListener.onDetach();
        }

        super.onDetachedFromWindow();
    }

    public interface ChattySpinnerListener {
        public void onDetach();
    }
}

And in your layout XML you want to make sure you specify this control instead of your normal spinner, and in your code set the listener with the implementation of whatever you want to have done when the spinner detaches. It will be up to you to figure out on the client side how to track whether something has been selected or not, maybe by setting a variable in the onItemSelected method that you give the selection listener.

Atiana answered 16/11, 2013 at 20:57 Comment(0)
S
0

I have had the same problem and I wanted to detecting when the the pop-up is closed, no matter if it was clicked outside or element was selected. I have no idea why Google don't wants to add simple listeners that we can use to detect such a crucial things. It is 2021 and there is still no good way of detecting it, really Google???

Obviously the solution is to use Reflection and getting access to the private variables. As @Kanth suggested we need to get access to the OnDismissListener(). But his answer is little outdated especially if you intend on using AppCompatSpinner

On further inspection we can see see that the AppCompatSpinner contains the private object 'mPopup' and it is from the interface SpinnerPopup type.

 private SpinnerPopup mPopup;

This interface is then used by the class DropdownPopup and is implementing its method and we need to look more closely at the implemented method show(). If we go down we can see that it sets the OnDismissListener(). So the listener is then used to remove the global layout listener using the method removeGlobalOnLayoutListener(). So we cannot directly change the setOnDismissListener because, the global layout listener that was previously added needs to be removed.

enter image description here

Now we need to find where exactly is the listener stored and then we need to get that value and keep it. Then set the new OnDismissListener where we can detect the closing of the pop-up. And finally it is very important to call the original OnDismissListener, so it can remove the global layout listener. So calling the method setOnDismissListener() is going inside the ListPopupWindow class and it is calling the same method from its 'mPopup' object.

enter image description here

And finally we get to the end method and the class that stores the listener reference object. The object is called mOnDismissListener and we need to hold reference to it when we set the new listener using the method setOnDismissListener().

enter image description here

So we need to override the spinner class and somehow override the OnDismissListener and to do that we need to go 3 parent classes down the rabbit hole.

CustomSpinner     
├── AppCompatSpinner    (mPopup)    
│   ├── ListPopupWindow (mPopup)     
|   |   ├── PopupWindow (mOnDismissListener) finally!!!     
|   |   |    
|────────      
  1. We create custom class CustomSpinner that implements the AppCompatSpinner class
  2. We need to get access to the private object mPopup from the AppCompatSpinner
  3. Then we need to get the private object mPopup from the ListPopupWindow class
  4. Then we need to get the private object mOnDismissListener from the PopupWindow class

Now we need to find a method that is called after the method show() from the DropdownPopup class that is declared inside the AppCompatSpinner class. But this method should be triggered before the original OnDismissListener is called. And this specials method is performClick(), which is called when the user clicks on the spinner and then the show() method is triggered that then attaches the original OnDismissListener.

So here are the steps that we need to do inside the performClick() method:

  1. Store the reference to the original OnDismissListener
  2. Set the new OnDismissListener that is called when the pop-up is closed
  3. Call a custom listener onPopUpClosedListener that we can use
  4. Finally the most important invoke the original OnDismissListener using the reference we stored previously in order to remove the global layout listener

So here is the final source code for our custom Spinner class

open class CustomSpinner: androidx.appcompat.widget.AppCompatSpinner {

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    lateinit var listPopupWindow: ListPopupWindow
    lateinit var onPopUpClosedListener: (dropDownMenu: DropDownMenu) -> Unit

    init {

        try {

            // get private property and make it accessible
            val listPopupWindowField = androidx.appcompat.widget.AppCompatSpinner::class.java.getDeclaredField("mPopup")
            listPopupWindowField.isAccessible = true

            // get the list popup window
            listPopupWindow = listPopupWindowField.get(this) as ListPopupWindow
            listPopupWindow.isModal = false

        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    override fun performClick(): Boolean {
        val returnValue = super.performClick()

        try {

            // get the popupWindow
            val popupWindowField = ListPopupWindow::class.java.getDeclaredField("mPopup")
            popupWindowField.isAccessible = true
            val popupWindow = popupWindowField.get(listPopupWindow) as PopupWindow

            // get the original onDismissListener
            val onDismissListenerField = PopupWindow::class.java.getDeclaredField("mOnDismissListener")
            onDismissListenerField.isAccessible = true
            val onDismissListener = onDismissListenerField.get(popupWindow) as PopupWindow.OnDismissListener

            // now override the original OnDismissListener
            listPopupWindow.setOnDismissListener {

                // here we detect when the drop down is dismissed and call the listener
                if (::onPopUpClosedListener.isInitialized) {
                    onPopUpClosedListener.invoke(this)
                }

                // now we need to call the original listener that will remove the global OnLayoutListener
                onDismissListener.onDismiss()
            }

        } catch (e: Exception) {
            e.printStackTrace()
        }

        return returnValue
    }

}

And then we can simply use the onPopUpClosedListener listener and detect when the pop-up is closed.

val customSpinner: CustomSpinner = findViewById(R.id.mySpinner)
customSpinner.onPopUpClosedListener = {
     
    // here we detect when the pop-up from our custom spinner is closed
}
Sunburst answered 13/9, 2021 at 2:34 Comment(0)
W
0

Right answer:

final ArrayAdapter adapterPlayerSelect = new ArrayAdapter(getActivity(), R.layout.item_simple_list, playModules);
        adapterPlayerSelect.setDropDownViewResource(R.layout.item_simple_list_bg);
        spPlayerSelect.setAdapter(adapterPlayerSelect);
        spPlayerSelect.setOnTouchListener((v, event) -> {
            if (event.getAction() == MotionEvent.ACTION_UP) {
                final ArrayAdapter adapterPlayerPopup = new ArrayAdapter(getActivity(), R.layout.item_simple_list_bg, playModules);
                final ListView lvMenu = new ListView(getActivity());
                lvMenu.setAdapter(adapterPlayerPopup);
                final androidx.appcompat.app.AlertDialog.Builder builder = new androidx.appcompat.app.AlertDialog.Builder(getActivity(), R.style.Theme_AppCompat_Dialog);
                builder.setView(lvMenu);
                final androidx.appcompat.app.AlertDialog dialog = builder.create();
                lvMenu.setOnItemClickListener((parent, view, position, id) -> {
                    spPlayerSelect.setSelection(position);
                    dialog.dismiss();
                });
                dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
                    @Override
                    public void onDismiss(DialogInterface dialog) {
                        //Action on dismiss
                    }
                });
                dialog.show();
                return true;
            }
            return false;
        });
Warrin answered 17/11, 2023 at 22:11 Comment(0)
F
-1

If you really don't need to use the spinner try using this code.
ListView inside Dialog. You can listen for the Cancel/dismiss event of the dialog(Same thing). You can use this in API 11.

final Dialog dialog = new Dialog(context);
            dialog.setContentView(R.layout.custom_list_popup);
            //dialog.setCancelable(false);
            dialog.setTitle("Title");

            dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
                @Override
                public void onCancel(DialogInterface dialog) {
                    //
                    //Do your onCancel things here
                    //
                }
            });


            final ListView listView = (ListView) dialog.findViewById(R.id.lv_sales_tax);
            listView.setAdapter(adapter);

            listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                    //
                    //Do your stuff here
                    //

                    dialog.dismiss();
                }
            });

            dialogButton.setVisibility(View.GONE);
            dialog.show();

        }
    });

contents of custom_list_popup.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="20dp"
android:orientation="vertical">

<ListView
    android:id="@+id/lv_sales_tax"
    android:divider="@drawable/list_divider"
    android:dividerHeight="20dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

</LinearLayout>
Fungiform answered 2/5, 2016 at 6:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.