Material Spinner (AutoCompleteTextView) - catch outside clicks - android

When using a spinner, it's the default behaviour that clicks outside of the spinner close the spinner but are NOT handled by the view underneath the click.
When switching to TextInputLayout + AutoCompleteTextView this behaviour is different, clicking on something outside the "spinner" closes the spinner AND the view underneath the touch does get the click event as well - this is very annoying and imho unexpected.
Can I somehow disable this behaviour to get the same behaviour as I get when using an old spinner?

Unfortunately no chances to catch the touch outside event using setOnDismissListener or dismissDropDown(); the latter gets called when item is selected.
But that would be possible by registering OnDismissListener on the instance of the inner popup window of the AutoCompleteTextView which is of type ListPopupWindow through reflections:
private fun getPopup(): ListPopupWindow? {
try {
val field = AutoCompleteTextView::class.java.getDeclaredField("mPopup")
field.isAccessible = true
return field.get(this) as ListPopupWindow
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
return null
}
Hopefully, the documentation can solve this to avoid this anti-pattern.
The OnDismissListener callback gets called on any type of dismissal of the menu; either clicking on items, touching outside the AutoCompleteTextView or hitting the soft keyboard back button. This can be distinguished by tagging the ACTT with the appropriate flag for each event; an enum is used for that in the below customized class):
import android.annotation.SuppressLint
import android.content.Context
import android.os.Build
import android.util.AttributeSet
import android.util.Log
import android.view.KeyEvent
import android.view.View
import android.view.ViewTreeObserver
import android.widget.AdapterView
import android.widget.AutoCompleteTextView
import android.widget.ListPopupWindow
import androidx.appcompat.widget.AppCompatAutoCompleteTextView
class TouchOutsideAutoCompleteTextView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : AppCompatAutoCompleteTextView(context, attrs), AdapterView.OnItemClickListener {
init {
super.setOnItemClickListener(this)
viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
viewTreeObserver
.removeOnGlobalLayoutListener(this)
else
viewTreeObserver
.removeGlobalOnLayoutListener(this)
// Registering the mPopup window OnDismissListener
adjustTouchOutside()
}
})
}
private var consumerListener: AdapterView.OnItemClickListener? = null
private enum class DismissEvent {
ON_ITEM_CLICK, // Should be set to enable the next touch dispatch event whenever the dismiss is due to a click on the menu item.
ON_TOUCH_OUTSIDE, // Should be set to disable the next touch dispatch event whenever the dismiss is due to a touch outside the menu.
ON_BACK_PRESSED // Should be set to enable the next touch dispatch event whenever the dismiss is due to the software keyboard back button pressed.
}
/*
* Called globally on any touch on the screen to consume the event if it returns true
* */
fun isDismissByTouchOutside() = tag == DismissEvent.ON_TOUCH_OUTSIDE
private fun isDismissByItemClickOrBackPressed() =
tag == DismissEvent.ON_ITEM_CLICK || tag == DismissEvent.ON_BACK_PRESSED
private fun setDismissToItemClick() {
tag = DismissEvent.ON_ITEM_CLICK
}
private fun setDismissToTouchOutside() {
tag = DismissEvent.ON_TOUCH_OUTSIDE
}
private fun setDismissToBackPressed() {
tag = DismissEvent.ON_BACK_PRESSED
}
fun clearDismissEvent() {
tag = null
}
#SuppressLint("DiscouragedPrivateApi")
private fun getPopup(): ListPopupWindow? {
try {
val field = AutoCompleteTextView::class.java.getDeclaredField("mPopup")
field.isAccessible = true
return field.get(this) as ListPopupWindow
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
return null
}
private fun adjustTouchOutside() {
getPopup()?.let {
it.setOnDismissListener {
if (isDismissByItemClickOrBackPressed()) {// Menu dismissal Event of clicking on the menu item or hitting the software back button
clearDismissEvent() // Neutralize the enum to allow the next touch dispatch event & for adding a chance of next dismissal decision
} else { // Menu dismissal Event of touching outside the menu
// Don't allow the next touch dispatch event
setDismissToTouchOutside()
}
}
}
}
override fun onItemClick(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
setDismissToItemClick()
consumerListener?.onItemClick(p0, p1, p2, p3)
}
override fun setOnItemClickListener(l: AdapterView.OnItemClickListener?) {
// DO NOT CALL SUPER HERE
// super.setOnItemClickListener(l)
consumerListener = l
}
override fun onKeyPreIme(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK && isPopupShowing)
setDismissToBackPressed()
return super.onKeyPreIme(keyCode, event)
}
}
The usage
Overriding dispatchTouchEvent() in the activity is required to consume the event for touching outside by checking isTouchOutsideDisabled(); and neutralize the dismissal event:
If the AutoCompleteTextView is in the activity then:
lateinit var autoCTV: TouchOutsideAutoCompleteTextView
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
if (autoCTV.isDismissByTouchOutside()) {
autoCTV.clearDismissEvent()
return true
}
return super.dispatchTouchEvent(ev)
}
If it is in some fragment, then the dispatchTouchEvent() need to have access to it:
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
val fragment = supportFragmentManager.findFragmentByTag("MyFragmentTag") as MyFragment
if (fragment.autoCTV.isDismissByTouchOutside()) {
fragment.autoCTV.clearDismissEvent()
return true
}
return super.dispatchTouchEvent(ev)
}

Related

How to listen to keyboard events in Jetpack Compose WITHOUT a TextField?

I am making a text input where I can NOT use TextField. I need text transformations that are not supported by TextField.
How can I get software/onscreen keyboard events?
Modifier.onKeyEvent() does not work for me as for some reason it only works for hardware keyboard.
This should be easy, right? I have not found a solution yet.
I found a way... Maybe not the most elegant, but it worked...
First, in your activity, capture the key event and send it via broadcast.
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
LocalBroadcastManager.getInstance(this).sendBroadcast(
Intent("key_up").apply {
putExtra("event", event)
}
)
return super.onKeyUp(keyCode, event)
}
Then declare the function below (wherever you want). It is responsible to open the keyboard.
suspend fun openKeyboard(context: Context) {
val myView = (context as Activity).findViewById<View>(android.R.id.content)
val inputMethodManager =
context.getSystemService(INPUT_METHOD_SERVICE) as? InputMethodManager
delay(500) // it didn't work without the delay...
inputMethodManager?.toggleSoftInputFromWindow(
myView.applicationWindowToken,
InputMethodManager.SHOW_FORCED,
0
)
}
Finally, here it is the composable.
#ExperimentalComposeUiApi
#Composable
fun FakeInput() {
var keyCode by remember { // last key pressed
mutableStateOf("")
}
val ctx = LocalContext.current
LaunchedEffect(Unit) {
openKeyboard(ctx)
}
DisposableEffect(Unit) {
val lbm = LocalBroadcastManager.getInstance(ctx)
// Receiver for key events
val receiver = object : BroadcastReceiver() {
override fun onReceive(ctx: Context?, intent: Intent?) {
intent?.getParcelableExtra<KeyEvent>("event")?.let { keyEvent ->
keyCode = keyEvent.displayLabel.toString()
}
}
}
// registering the receiver
lbm.registerReceiver(receiver, IntentFilter("key_up"))
onDispose {
// unregistering when the composable is disposed
lbm.unregisterReceiver(receiver)
}
}
Box(Modifier.fillMaxSize()) {
Text(text = keyCode, Modifier.align(Alignment.Center))
}
}
Here is the result:

How to Parcelize a MutableList in kotlin?

I have an App that lets users drag and draw boxes on a custom view. I want to persist the state of these boxes(list of boxes) across orientation change using onSavedInstanceState(): Parcelable and onRestoreInstanceState(state: Parcelable). However, I don't know how to store a MutableList because the only available function is putParcelableArrayList. Please how do I parcelize a Mutable List to persist the boxes across rotation? I know the docs said its possible but I don't know how to. Here is the code.
#Parcelize
class Box(private val start: PointF) : Parcelable {
// When a user touches BoxDrawingView, a new box will be created and added to the list of existing boxes.
var end: PointF = start
val left: Float
get() = start.x.coerceAtMost(end.x)
val right: Float
get() = start.x.coerceAtLeast(end.x)
val top: Float
get() = start.y.coerceAtMost(end.y)
val bottom: Float
get() = start.y.coerceAtLeast(end.y)
}
My custom View
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PointF
import android.os.Bundle
import android.os.Parcelable
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
/** This Class is where we setup our custom View and write the Implementation for listening to touch events from the USER and draw boxes on the Screen.**/
private const val TAG = "BoxDrawingView"
private const val BOX_STATE = "box"
private const val VIEW_STATE = "view"
class BoxDrawingView(context: Context, attrs: AttributeSet? = null) :
View(context, attrs) {
private var currentBox: Box? = null
private var boxen = mutableListOf<Box>() // list of boxes to be drawn out on the screen
private val boxPaint = Paint().apply {
color = 0x22ff0000
}
private val backGroundPaint = Paint().apply {
color = 0xfff8efe0.toInt()
}
init {
isSaveEnabled = true
}
override fun onSaveInstanceState(): Parcelable {
val bundle = Bundle()
bundle.putParcelableArrayList(BOX_STATE, boxen) // type mismatch error because of mutableList passed to ArrayList
bundle.putParcelable(VIEW_STATE, super.onSaveInstanceState())
return bundle
}
override fun onRestoreInstanceState(state: Parcelable?) {
var viewState = state
if (viewState is Bundle) {
boxen = viewState.getParcelableArrayList<Box>(BOX_STATE)?.toMutableList() ?: mutableListOf()
viewState = viewState.getParcelable(VIEW_STATE)
}
super.onRestoreInstanceState(state)
}
override fun onDraw(canvas: Canvas) {
// Fill in the background
canvas.drawPaint(backGroundPaint)
boxen.forEach { box ->
canvas.drawRect(box.left, box.top, box.right, box.bottom, boxPaint)
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
val current = PointF(event.x, event.y)
var action = ""
when(event.action) {
MotionEvent.ACTION_DOWN -> {
action = "ACTION_DOWN"
// Reset drawing state
currentBox = Box(current).also {
boxen.add(it)
}
}
MotionEvent.ACTION_MOVE -> {
action = "ACTION_MOVE"
// update the currentBox.end as the user moves his/her finger around the screen
updateCurrentBox(current)
}
MotionEvent.ACTION_UP -> {
action = "ACTION_UP"
// tells the last report of the currentBox as the user's finger leaves the screen
updateCurrentBox(current)
currentBox = null
}
MotionEvent.ACTION_CANCEL -> {
action = "ACTION_CANCEL"
currentBox = null
}
}
// this is a log message for each of the 4 Event actions
Log.i(TAG, "$action at x=${current.x}, y=${current.y}")
return true
}
Changing the boxen type to arrayList() worked. Turns out arrayList works as a mutable list under the hood. Also David wasser's answer in the comments also worked.

ActionMode callback never calls onCreateActionMode when started in fragment's OnViewCreated

#ExperimentalCoroutinesApi
class WeekdayTimesFragment #Inject constructor(viewModelFactory: SavedStateViewModelFactory.Factory)
:Fragment(R.layout.fragment_weekday_times), WeekdayAlarmTimesAdapter.OnWeekdayAlarmTimePressed, ActionModeListener {
private lateinit var weekdayAlarmTimesAdapter: WeekdayAlarmTimesAdapter
private val weekdayTimesViewModel
by viewModels<WeekdayTimesViewModel> { viewModelFactory.create(this) }
override val actionCallback = PrimaryActionCallback(this)
private var weekday: Int = 0
private val isActionModeActive: Boolean
get() = weekdayTimesViewModel.isActionModeActive
private val numSelected: Int
get() = weekdayTimesViewModel.selectedTimePositions.size
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
weekday = requireArguments().get("WEEKDAY") as Int
if(isActionModeActive) { //action mode may be active prior to rotation
startActionMode() //for some reason, starting action mode here does not work....
}
initRecyclerView() //will determine what recyclerview items are selected via the viewmodel
weekdayTimesViewModel.getTimesAndAlarmsForSelectedWeekday(Weekday[weekday])
.observe(viewLifecycleOwner) { times->
weekdayAlarmTimesAdapter.submitWeekdayTimes(times)
}
}
override fun alarmTimePressed(wasSelected: Boolean, position: Int, time: WeekdayTime) {
if(wasSelected) { //this item was selected prior to being selected, effectively unselecting it
weekdayTimesViewModel.selectedTimePositions.remove(position)
} else {
weekdayTimesViewModel.selectedTimePositions[position] =
Time(time.hour, time.minute, Weekday[weekday], time.alarm.alarmId, time.isDisabled, time.id)
}
if(numSelected == 0 && isActionModeActive) { //no more times are selected, turn off action mode
destroyActionMode() //need to de-select recyclerview items
} else {
if (!isActionModeActive) {
startActionMode()
} else {
actionCallback.changeTitle("$numSelected selected")
}
}
}
private fun startActionMode() {
actionCallback
.startActionMode(
requireView(),
R.menu.toolbar_contextual_menu,
"$numSelected selected")
//fragment hosting the viewpager
(requireParentFragment() as ActionModeListenerController).currentActionModeListener = this
weekdayTimesViewModel.isActionModeActive = true
}
override fun onActionItemClick(item: MenuItem?) {
item?.let {
when(it.itemId){
R.id.toolbar_disable_alarm-> {
requireContext().createGenericAlertDialog(
message = if(numSelected == 1) "Are you sure you want to disable this alarm?"
else "Are you sure you want to disable these alarms?",
positiveListener = { _, _ ->
weekdayTimesViewModel.disableSelectedTimes()
showUndoSnackbar(true)
}
).show()
}
R.id.toolbar_delete_alarms-> {
requireContext().createGenericAlertDialog(
message = if(numSelected == 1) "Are you sure you want to delete this alarm?"
else "Are you sure you want to delete these alarms?",
positiveListener = { _, _ ->
weekdayTimesViewModel.deleteSelectedTimes()
showUndoSnackbar(false)
}
).show()
}
}
}
}
//when we aren't destroying the view but need to end action mode (changing tab or after we confirm an action)
//we may want to do specific things specific to certain listeners, e.g. de-select certain recyclerview items.
override fun destroyActionMode() {
actionCallback.finishActionMode()
weekdayTimesViewModel.isActionModeActive = false
//also un-select all of the elements that have been selected
weekdayTimesViewModel.selectedTimePositions.clear()
//redraw recyclerview as well
rv_weekday_times.resetAdapterState()
}
private fun showUndoSnackbar(disablingTimes: Boolean){
val message =
if(disablingTimes) "Your alarm times have been disabled." else "Your alarm times have been deleted."
Snackbar.make(requireView(), message, Snackbar.LENGTH_LONG).setAction("UNDO"){
if(disablingTimes)
weekdayTimesViewModel.restoreDisabledTimes()
else
weekdayTimesViewModel.restoreDeletedTimes()
}.setActionTextColor(ContextCompat.getColor(requireContext(), R.color.colorPrimary))
.setAnchorView(requireActivity().bottom_nav_view).show()
}
//when rotating screen or navigating we can just end action mode. The state of whether we are
//in action mode either doesn't matter (navigation) or is already persisted, along with the selected
//items in the viewmodel(rotation)
override fun onDestroyView() {
actionCallback.finishActionMode()
super.onDestroyView()
}
private fun initRecyclerView() {
weekdayAlarmTimesAdapter = WeekdayAlarmTimesAdapter(this, weekdayTimesViewModel.selectedTimePositions.keys)
rv_weekday_times.apply {
adapter = weekdayAlarmTimesAdapter
layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
}
weekdayAlarmTimesAdapter.selectedItems.clear()
//clears selected items as these items no longer remain consistently for the lifecycle of the view
}
}
So my ActionMode works great when I get a callback (alarmTimePressed) which is through a click listener set in the recyclerview's OnBindViewHolder. I start my ActionMode and it works great, I save whether ActionMode is active in my viewmodel, which works well upon rotation, as if it is active prior to rotation, it still remains active. As you can see in my OnViewCreated, I call startActionMode() when ActionMode is meant to be active. The problem is that when I start it from there, the onCreateActionMode callback is never called. This is strange because even after rotating, if I select an item in the RecyclerView it correctly launches ActionMode. The view I am passing to startActionMode is the same in both cases. If you need the code for PrimaryActionCallback, it's here: https://hastebin.com/vepujuhefe.m. Didn't think it was very necessary but in case you felt like it was necessary to see :). Anyone know what's going on here?

recyclerview-selection: stop auto item deselection when touching to the blank space inside the recyclerview

I am trying to use the recyclerview-selection library in my project. I followed this tutorial:
https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504
Everything workes fine. But I have a problem. If I tap/touch any blank space inside the RecyclerView, all the selected elements got deselected! I don't find any method or solution to disable this. What should I do?
I am using implementation 'androidx.recyclerview:recyclerview-selection:1.1.0-rc01' in my project.
Edit 1:
I set the RecyclerView background red to describe my problem. Here, blue items are selected items. If I click any red area, then all the selected items got unselected! The select and deselect should only be done by clicking the items. So, I need to disable this feature (or bug!), that unselect all items!
Example project: https://github.com/ImaginativeShohag/multiselection
Solution is to have an out of context selection item like:
class ItemLookup(private val recyclerView: RecyclerView) : ItemDetailsLookup<Long>() {
private val outOfContextSelection = object : ItemDetails<Long>() {
override fun getPosition(): Int = OUT_OF_CONTEXT_POSITION.toInt()
override fun getSelectionKey() = OUT_OF_CONTEXT_POSITION
}
override fun getItemDetails(e: MotionEvent): ItemDetails<Long>? {
recyclerView.findChildViewUnder(e.x, e.y)?.let {
return (recyclerView.getChildViewHolder(it) as?
SelectorBarAdapter.SelectorBarViewHolder)?.itemDetails
}
return outOfContextSelection
}
companion object {
const val OUT_OF_CONTEXT_POSITION = 10000L
}
}
so that when a view which is not one of our clickable elements is clicked we can identify further on a predicate like follows:
class SingleSelectionPredicate : SelectionTracker.SelectionPredicate<Long>() {
override fun canSetStateForKey(key: Long, nextState: Boolean): Boolean {
// warranties that an item can't be unselected on click
// warranties that clicks out of the item's scope are disabled
return nextState && key != ItemLookup.OUT_OF_CONTEXT_POSITION
}
override fun canSetStateAtPosition(position: Int, nextState: Boolean) = true
override fun canSelectMultiple() = false
}
You create your own SelectionTracker.SelectionPredicate<Long>.
Override the method canSetStateForKey(key: Long, nextState: Boolean)
like this:
override fun canSetStateForKey(#NonNull key: Long, nextState: Boolean): Boolean {
rv.findViewHolderForItemId(key)?.let { holder -> adapter.canSetStateForItem(holder as YourItemHolder<YourItem>, nextState)}
return true
}
and in your ViewHolder check if the item is already selected if so return false or vice versa.
Edit:
You can also define a "Selection Hotspot" by inSelectionHotspot(e: MotionEvent) in your ItemDetailsLookup.ItemDetails.
In your ViewHolder you can then check if the TouchEvent is in an area that requires a select or unselect.
for example:
override fun inSelectionHotspot(e: MotionEvent): Boolean {
val rect = Rect()
itemView.getGlobalVisibleRect(rect)
if (rect.contains(e.rawX.toInt(), e.rawY.toInt())) {
// in select region
} else {
// not in select region
}
}

AutoCompleteTextView: detecting when dropdown is dismissed and item is NOT selected

I need to know when the user taps outside of the AutoCompleteTextView dropdown in order to dismiss it (i.e. they dismiss the popup with selecting an item in the list). I've setup the setOnDismissListener() as shown here:
mAutoView.setOnDismissListener(new AutoCompleteTextView.OnDismissListener() {
#Override
public void onDismiss() {
CharSequence msg = "isPerformingCompletion = " + mAutoView.isPerformingCompletion() +
" Item selected at = " + mAutoView.getListSelection();
Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show();
}
});
And an OnItemClickListener like this:
private AdapterView.OnItemClickListener mAutocompleteClickListener
= new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// get selected item and pass it to result callback
}
};
The onDismiss event fires before the onItemClick event, and unfortunately, neither the "isPerformingCompletion()" nor the "getListSelection()" methods return a value until the onItemClick event fires.
Can anyone suggest an approach to detecting a dismiss without a list selection?
Below piece of code will detect that if user dismiss the dropdown of autocompletetextview, Then onDismiss it will check the selected input using Geocode API, If it is having a result then selected input is valid otherwise input is not valid, So In that case, Entered text will be vanish in bit seconds.
mAutoView.setOnDismissListener {
try {
val fromLocationName = Geocoder(context).getFromLocationName(mAutoView.getText().toString(), 1)
if (fromLocationName != null && fromLocationName.isNotEmpty()) {
Log.d(TAG, "Address valid")
} else {
mAutoView.setText("")
Log.d(TAG, "Address not valid")
}
} catch (e: Exception) {
mAutoView.setText("")
Log.d(TAG, "Address not valid with Exception")
}
}
Means you need a kind of validator in OnDismiss, That will check the input text is valid or not, Based on validation you can indicate to user that entered input is valid or not.
AutoCompleteTextView: detecting when dropdown is dismissed and item is NOT selected
If I understood you well, you need to catch the event of dismissing the draopDown by touching outside of it instead of choosing an item.
For some reason, the setOnDismissListener doesn't work for me. I couldn't find a clue without touching the inner ListPopupWindow which get called on either event (item click or touch outside).
The event is differentiated by registering an inner OnItemClickListener listener to set the tag of the view to some value that indicates that BY_ITEM_CLICK ; if the tag is anything else; it will be considered a touch outside event.
Here is a custom AutoCompleteTextView that can be used for that:
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.View
import android.widget.AdapterView
import android.widget.AutoCompleteTextView
import android.widget.ListPopupWindow
import androidx.appcompat.widget.AppCompatAutoCompleteTextView
class OnDismissAutoCompleteTextView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : AppCompatAutoCompleteTextView(context, attrs), AdapterView.OnItemClickListener {
interface OnMenuDismissListener {
fun onTouchOutside()
fun onItemClick()
fun onBackPressed()
}
companion object {
// set the tag to this value when an item is clicked to dismiss the menu
const val BY_ITEM_CLICK = "BY_ITEM_CLICK"
// set the tag to this value when the back is pressed to dismiss the menu
const val BY_BACK_PRESSED = "BY_BACK_PRESSED"
}
init {
super.setOnItemClickListener(this)
}
var onMenuDismissListener: OnMenuDismissListener? = null
private var consumerListener: AdapterView.OnItemClickListener? = null
#SuppressLint("DiscouragedPrivateApi")
private fun getPopup(): ListPopupWindow? {
try {
val field = AutoCompleteTextView::class.java.getDeclaredField("mPopup")
field.isAccessible = true
return field.get(this) as ListPopupWindow
} catch (e: NoSuchFieldException) {
e.printStackTrace()
} catch (e: IllegalAccessException) {
e.printStackTrace()
}
return null
}
private fun setupDismissListener() {
getPopup()?.let {
it.setOnDismissListener {
when (tag) {
BY_ITEM_CLICK -> onMenuDismissListener?.onItemClick() // Menu dismissal Event of clicking on the menu item
BY_BACK_PRESSED -> onMenuDismissListener?.onBackPressed()
else -> onMenuDismissListener?.onTouchOutside() // Menu dismissal Event of touching outside the menu
}
// reset the tag for the next dismissal
tag = null
}
}
}
override fun onPreDraw(): Boolean {
// Registering the mPopup window OnDismissListener
setupDismissListener()
return super.onPreDraw()
}
override fun onItemClick(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) {
tag = BY_ITEM_CLICK
if (consumerListener != null) {
consumerListener!!.onItemClick(p0, p1, p2, p3); }
}
override fun setOnItemClickListener(l: AdapterView.OnItemClickListener?) {
// DO NOT CALL SUPER HERE
// super.setOnItemClickListener(l)
consumerListener = l
}
override fun onKeyPreIme(keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK && isPopupShowing)
tag = BY_BACK_PRESSED
return super.onKeyPreIme(keyCode, event)
}
}
Usage:
myAutoCompleteTV.onMenuDismissListener = object :
OnDismissAutoCompleteTextView.OnMenuDismissListener {
override fun onTouchOutside() {
// Menu dismiss is due to touch outside event
Toast.makeText(context, "touch outside", Toast.LENGTH_SHORT).show()
}
override fun onItemClick() {
// Menu dismiss is due to clicking on an item
Toast.makeText(context, "item clicked", Toast.LENGTH_SHORT).show()
}
override fun onBackPressed() {
Toast.makeText(context, "back pressed", Toast.LENGTH_SHORT).show()
}
}

Categories

Resources