Поймать событие, когда выпадающий счетчик отклонен
Я хочу поймать событие, когда выпадающее меню отклонено. Мы можем поймать это, когда пользователь нажимает на любой элемент в onItemSelected(). Но я хочу поймать, даже когда пользователь касается вне выпадающей области или кнопки назад, поскольку они тоже заставляют его исчезнуть. По этим двум причинам, когда я наблюдал журнал, он говорит: "Попытка завершить входное событие, но приемник входного события уже был удален"
Я наблюдал исходный код, он печатается из InputEventReceiver.java в методе finishInputEvent(событие InputEvent, логическая обработка). Но это последний метод, поэтому нет смысла его переопределять. Может кто-нибудь предложить, пожалуйста, способ обработки, когда выпадающий список отклонен в этих случаях?
5 ответов
Я использовал Popup Menu
вместо прядильщика. Потому что, насколько мне известно, событие dismiss не может быть поймано с помощью счетчика, но с помощью всплывающего меню я сделал это, установив onDismissListerner()
во всплывающее меню
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.
}
});
Как насчет поиска другого события, как onDetachFromWindow
? У прядильщика нет никаких регулярных событий жизненного цикла, с которыми мы много работаем - было бы неплохо иметь onStop
или же onDestroy
работать с. Конечно, вам придется расширить класс spinner и создать интерфейс для определения собственного слушателя:
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();
}
}
И в своем макете XML вы хотите убедиться, что вы указываете этот элемент управления вместо обычного счетчика, а в своем коде задаете слушателю реализацию того, что вы хотите сделать, когда отсоединитель счетчика. Вам будет необходимо выяснить на стороне клиента, как отслеживать, было ли что-то выбрано или нет, возможно, установив переменную в onItemSelected
метод, который вы даете слушателю выбора.
У меня была такая же проблема, и я хотел определить, когда всплывающее окно закрывается, независимо от того, был ли он нажат снаружи или был выбран элемент. Я понятия не имею, почему Google не хочет добавлять простые слушатели, которые мы можем использовать для обнаружения таких важных вещей. На дворе 2021 год, а надежного способа его обнаружить все еще нет, правда, Google???
Очевидно, решение состоит в том, чтобы использовать Reflection и получить доступ к закрытым переменным. Как предположил @Kanth, нам нужно получить доступ к OnDismissListener() . Но его ответ немного устарел, особенно если вы собираетесь использовать AppCompatSpinner.
При дальнейшем осмотре мы видим, что AppCompatSpinner содержит частный объект mPopup и принадлежит к типу интерфейса SpinnerPopup .
private SpinnerPopup mPopup;
Затем этот интерфейс используется классом DropdownPopup и реализует свой метод, и нам нужно более внимательно изучить реализованный метод show (). Если мы спустимся, мы увидим, что он устанавливает OnDismissListener(). Таким образом, прослушиватель затем используется для удаления прослушивателя глобального макета с помощью метода removeGlobalOnLayoutListener(). Таким образом, мы не можем напрямую изменить setOnDismissListener, потому что ранее добавленный прослушиватель глобального макета необходимо удалить.
Теперь нам нужно найти, где именно хранится слушатель, а затем нам нужно получить это значение и сохранить его. Затем установите новый OnDismissListener, где мы можем обнаружить закрытие всплывающего окна. И, наконец, очень важно вызвать исходный OnDismissListener, чтобы он мог удалить прослушиватель глобального макета. Таким образом, вызов метода setOnDismissListener () происходит внутри класса ListPopupWindow и вызывает тот же метод из своего объекта mPopup.
И, наконец, мы переходим к конечному методу и классу, в котором хранится объект ссылки слушателя. Объект называется mOnDismissListener, и нам нужно удерживать ссылку на него, когда мы устанавливаем нового слушателя с помощью метода setOnDismissListener().
Итак, нам нужно переопределить класс счетчика и каким-то образом переопределить OnDismissListener, а для этого нам нужно пройти 3 родительских класса в кроличью нору.
CustomSpinner
├── AppCompatSpinner (mPopup)
│ ├── ListPopupWindow (mPopup)
| | ├── PopupWindow (mOnDismissListener) finally!!!
| | |
|────────
- Мы создаем собственный класс CustomSpinner, который реализует класс AppCompatSpinner.
- Нам нужно получить доступ к приватному объекту mPopup из AppCompatSpinner
- Затем нам нужно получить приватный объект mPopup из класса ListPopupWindow.
- Затем нам нужно получить приватный объект mOnDismissListener из класса PopupWindow.
Теперь нам нужно найти метод, который вызывается после метода show () из класса DropdownPopup, объявленного внутри класса AppCompatSpinner. Но этот метод должен запускаться до вызова исходного OnDismissListener. И этот специальный метод - performClick() , который вызывается, когда пользователь нажимает на счетчик, а затем запускается метод show (), который затем присоединяет исходный OnDismissListener.
Итак, вот шаги, которые нам нужно сделать внутри метода performClick():
- Сохраните ссылку на исходный OnDismissListener
- Установите новый OnDismissListener, который вызывается при закрытии всплывающего окна.
- Вызов настраиваемого прослушивателя onPopUpClosedListener, который мы можем использовать
- Наконец, наиболее важные вызовы исходного OnDismissListener, используя ссылку, которую мы сохранили ранее, чтобы удалить прослушиватель глобального макета.
Итак, вот окончательный исходный код для нашего настраиваемого класса Spinner.
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
}
}
А затем мы можем просто использовать прослушиватель onPopUpClosedListener и определять, когда всплывающее окно закрывается.
val customSpinner: CustomSpinner = findViewById(R.id.mySpinner)
customSpinner.onPopUpClosedListener = {
// here we detect when the pop-up from our custom spinner is closed
}
Правильный ответ:
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;
});
Если вам действительно не нужно использовать спиннер, попробуйте использовать этот код.
ListView внутри диалога. Вы можете прослушать событие отмены / отклонения диалога (та же самая вещь). Вы можете использовать это в 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();
}
});
содержимое 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>