I have an activity with a set of buttons on it, it resembles a NumPad keyboard.
My purpose: to do some terminal for input data with help of little (hard) “usb NumPad keyboard” – so interface looks as NumPad – just a set of Buttons.
I want to handle all the keyboard events to do with them what I need to do (my own function for each button). Overrided functions onKeyUp and onKeyDown – and they do all that I need, except handling the Enter key. In these two functions it’s not an event at all as I see.
On Enter activity opens menu, so Enter is some special function – not for me, but for system.
All the topics here that I saw (how to handle “Enter”) are about soft keyboard or EditView. I don’t have on my activity any editable, I just want to catch Enter event, or maybe possible link Enter with some of the Buttons on activity.
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
Toast.makeText(this,keyCode.toString(),Toast.LENGTH_SHORT).show()
val res: Int = when (keyCode) {
KeyEvent.KEYCODE_NUMPAD_0 -> 0
KeyEvent.KEYCODE_0 -> 0
KeyEvent.KEYCODE_NUMPAD_1 -> 1
KeyEvent.KEYCODE_1 -> 1
KeyEvent.KEYCODE_DPAD_DOWN_LEFT -> 1
KeyEvent.KEYCODE_NUMPAD_DOT -> 10
KeyEvent.KEYCODE_NUM_LOCK -> 11
KeyEvent.KEYCODE_NUMPAD_ENTER -> 16 //never happens
else -> -1
}
if (res>=0) doAction(res)
return if (res == -1) super.onKeyUp(keyCode, event)
else true
}
Use dispatchKeyEvent to handle enter:
override fun dispatchKeyEvent(event:KeyEvent):Boolean {
if (event.getAction() === KeyEvent.ACTION_UP)
{
Toast.makeText(this,event.getKeyCode().toString(),Toast.LENGTH_SHORT).show()
return true
}
}
Related
I am using viewpager2 in my application. I enabled auto slide using rxjava2 (observable interval). I want to stop auto slide when user touch the viewpager and after touch finishes start auto slide. But I can not find proper way to detect touch finish event. I tried action_up, but it triggers only when fast touch like click. It's not detect when user touch 2 second and finish touch.
Observable.interval(SLIDER_DELAY, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
if (!homeAdapter.touchStatus()) {
if (viewPagerSlider.currentItem < homeAdapter.itemCount - 1) {
viewPagerSlider.setCurrentItem(
viewPagerSlider.currentItem + 1,
true
)
} else {
viewPagerSlider.setCurrentItem(0, true)
}
}
}
HomeAdapter
itemView.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> isTouched = true
MotionEvent.ACTION_UP -> isTouched = false
else -> {}
}
true
}
fun touchStatus() = this.isTouched
How can I detect when user finished touch event after some time?
I have a Bottom Sheet Dialog Fragment which contains four Fragment with ViewPager.
I want to call a method when onBackPressed clicked in Bottom Sheet Dialog Fragment. Implemented OnBackPressedCallback in my OnCreateView but it is not triggered. Any one have a idea why it is not called?
val callback = object : OnBackPressedCallback(true */ true means that the callback is enabled /*) {
override fun handleOnBackPressed() {
// Show your dialog and handle navigation
LogUtils.d("Bottom Sheet -> Fragment BackPressed Invoked")
}
}
// note that you could enable/disable the callback here as well by setting callback.isEnabled = true/false
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
I found this thread while looking for a solution to the same problem that exists in DialogFragment. The answers are in the comments above, but for completeness here is the information aggregated:
Solution
In your DialogFragment override onCreateDialog and set an OnKeyListener:
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
setOnKeyListener { _: DialogInterface, keyCode: Int, keyEvent: KeyEvent ->
if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.action == KeyEvent.ACTION_UP) {
// <-- Your onBackPressed logic here -->
return#setOnKeyListener true
}
return#setOnKeyListener false
}
}
}
Explanation
From an issue raised against requireActivity().onBackPressedDispatcher.addCallback not working for DialogFragments (https://issuetracker.google.com/issues/149173280):
Dialogs are separate windows that always sit above your activity's window. This means that the dialog will continue to intercept the system back button no matter what state the underlying FragmentManager is in, or what code you run in your Activity's onBackPressed() - which is where the OnBackPressedDispatcher plugs into.
Essentially the onBackPressedDispatcher is the wrong tool for the job when using any component that utilises Dialogs because of how they behave within an Application and exist outside (on top) of Activities.
#ITJscott has explained very well.
in case any one struggling in understanding/ implementing kotlin code here is JAVA code snippet for the same.
#NonNull
#Override
public Dialog onCreateDialog(#Nullable Bundle savedInstanceState) {
Dialog mDialog = super.onCreateDialog(savedInstanceState);
mDialog.setOnKeyListener((dialog, keyCode, event) -> {
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
// <-- Your onBackPressed logic here -->
requireActivity().onBackPressed();
return true;
}
return false;
});
return mDialog;
}
This behaviour can also occur if you've set bottom sheet to be non-cancelable using .
So, to avoid this, you can use below code which detects certain events like keypad entry or back press. If you want to perform other action on other events, you can add the code here.
bottomSheetDialog.setOnKeyListener { _, keyCode, _ ->
if (keyCode == KeyEvent.KEYCODE_BACK) {
onBackPressed()
return#setOnKeyListener true
} else {
return#setOnKeyListener false
}
}
I have Extension of EditText where I'm listenig to events from keyboard of EditText. I need to know when user press any button for showing (or not showing) error. So I make Observable for keys (rxbinding2) and I'm getting any press but when I press back button and cursor still in this EditText method onBackPressed doesn`t work.
How to filter onBack pressed?
fun EditText.changeWithFormatting(formatter: (String) -> String): Observable<String> {
return Observable.merge(
afterTextChangeEvents()
.map { editableText },
keys()
.filter { it.action == KeyEvent.ACTION_UP }
.map { editableText }
)
.map { changeText(formatter(it.toString())) }
}
Just listen for the keydown event. This should trigger before it gets handed to your UI element. super will pass it to the parent to go normal flow. I can't remember if it's return true or false, but I think it's true for handled.
#Override
public boolean onKeyDown(int keyCode, KeyEvent event)
{
if ((keyCode == KeyEvent.KEYCODE_BACK))
{
onBackPressed();
return true;
}
return super.onKeyDown(keyCode, event);
}
How to override performClick in Kotlin to avoid warning?
next.setOnTouchListener(View.OnTouchListener { view, motionEvent ->
when (motionEvent.action){
MotionEvent.ACTION_DOWN -> {
val icon: Drawable = ContextCompat.getDrawable(activity.applicationContext, R.drawable.layer_bt_next)
icon.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY)
next.setImageDrawable(icon)
}
MotionEvent.ACTION_UP -> {
//view.performClick()
next.setImageResource(R.drawable.layer_bt_next)
}
}
return#OnTouchListener true
})
view.performClick does not work.
Try this way :
next.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> //Do Something
}
return v?.onTouchEvent(event) ?: true
}
})
Okay, I solved my own problem by overriding the OnTouch listener.
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
when (view) {
next -> {
Log.d("next", "yeyy")
when (motionEvent.action){
MotionEvent.ACTION_DOWN -> {
val icon: Drawable = ContextCompat.getDrawable(activity.applicationContext, R.drawable.layer_bt_next)
icon.setColorFilter(Color.GRAY, PorterDuff.Mode.MULTIPLY)
next.setImageDrawable(icon)
}
MotionEvent.ACTION_UP -> {
view.performClick()
next.setImageResource(R.drawable.layer_bt_next)
}
}
}
previous -> {
//ingredients here XD
}
}
return true
}
And in that way, I can call single onTouch and implement it to many button and also can use the onClick by :
view.performClick()
Don't forget to implement :
View.OnTouchListener
And set the listener :
next.setOnTouchListener(this)
previous.setOnTouchListener(this)
I don't think your solution will actually solve them problem presented by the warning. The warning states that certain accessibility functions use performClick() to activate buttons. If you look in the View class, the performClick() funtions calls the onClickListener directly, meaning the code in the onTouchListener will not be executed (next.setImageResource(R.drawable.layer_bt_next)) for these accessibility functions, since the view will never be physically touched, and thus your onTouch code won't run. You have to do one of either:
Subclass the view you are setting the onTouchListener on, and override performClick to execute the code, or
Set an onClickListener on the view that executes the code.
You could just implement onClickListener in your onTouchListener class and manually call onClick() from your onTouchListener (where you have view.performClick() now), and then move your executable code to the onClick override. You would also have to set BOTH onTouchListener and onClickListener on your views.
I'm not sure this is the same issue you saw, but since I found this page searching for my issue, I thought I'd add my experience to help others :)
In my case the warning was being generated because the nullable view could have been of type Void. Calling the following:
nullableView?.setOnTouchListener(this)
produced the error:
Custom view Void has setOnTouchListener called on it but does not override performClick
Performing a null check and casting to a View before setting the listener solved for me in this case, since View will override performClick:
if (nullableView != null) (nullableView as View).setOnTouchListener(this)
After a ton of digging, and not being able to fix my variation of this issue with anything in this thread, I finally found a fix. Maybe it will work for some of you. I had this widget listener setter in my MainActivity onCreate function:
findViewById<TextView>(R.id.tvAnimalList).setOnTouchListener { v, event ->
mGestureDetector.onTouchEvent(event)
}
Which results in the warnings:
'onTouch' lambda should call 'View#performClick' when a click is detected
Custom view "TextView" has 'setOnTouchListener' called on it but does not override 'performClick'
First, I added a call to v.performClick(), which got rid of the first warning. Like this:
findViewById<TextView>(R.id.tvAnimalList).setOnTouchListener { v, event ->
v.performClick()
mGestureDetector.onTouchEvent(event)
}
I got rid of the second warning by changing the findViewById cast from <TextView> to <View>. Here's my warning-free result:
findViewById<View>(R.id.tvAnimalList).setOnTouchListener { v, event ->
v.performClick()
mGestureDetector.onTouchEvent(event)
}
private fun closeKeyboard(binding: ContollerMeterBinding) {
binding.scrollView.apply {
setOnTouchListener(OnTouchListener { v, event ->
if (event != null && event.action == MotionEvent.ACTION_MOVE) {
val imm =
activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
val isKeyboardUp = imm.isAcceptingText
if (isKeyboardUp) {
imm.hideSoftInputFromWindow(v.windowToken, 0)
}
}
performClick()
false
})
}
}
This works for me: (not directly related to onTouch event but yields the same warning, might be helpful to someone)
takePhotoButton.setOnTouchListener { _, motionEvent ->
when (motionEvent.action) {
MotionEvent.ACTION_DOWN -> {
//when user touch down
}
MotionEvent.ACTION_UP -> {
//when user touch release
}
}
true
}
I'm trying to write an observable that would generate repeated events while the user holds down a view. My code below works well, but only the first time (e.g. if the user presses the button again, nothing happens). Can you please advise what am I doing wrong and what is best practice for this?
val touches = RxView.touches(previousButton)
touches
.filter({ event -> event.action == MotionEvent.ACTION_DOWN })
.flatMap({
Observable.interval(500, 50, TimeUnit.MILLISECONDS)
.takeUntil(touches.filter({event -> event.action == MotionEvent.ACTION_UP}))
}).subscribe({ println("down") })
The problem is that the RxView.touches observable cannot exist for more than 1 source. This means when the subscription inside of the flatMap happens it breaks the original subscription used to trigger the flatMap, making it never occur again.
There are two possible ways around this:
Use .publish(...) to share the source of events instead of using touches.
Map the events into a Boolean on/off observable, then switchMap the appropriate actions based on the current value of the observable.
1.
touches.publish { src ->
src.filter(...)
.flatMap {
Observable.interval(...)
.takeUntil(src.filter(...))
}
}
2.
touches.filter {
it.action == MotionEvent.ACTION_DOWN
or it.action == MotionEvent.ACTION_UP
}
.map { it.action == MotionEvent.ACTION_DOWN }
.distinctUntilChanged() // Avoid repeating events
.switchMap { state ->
if (state) {
Observable.interval(...)
} else {
Observable.never()
}
}