The following are two blocks of code that should essentially do the same thing. But the second one does not execute the onEditorAction while the first one does. What is it about the second one that is different that prevents it from executing the code? NOTE: Only one of these is present in the code and not both.
// This one works
this.setOnEditorActionListener { v, actionId, event ->
if(actionId == EditorInfo.IME_ACTION_SEARCH){
mOnRunSearchCallback()
true
} else {
false
}
}
// This one does not work
this.setOnEditorActionListener(object : TextView.OnEditorActionListener {
override fun onEditorAction(v: TextView, actionId: Int, event: KeyEvent): Boolean {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
mOnRunSearchCallback()
return true
}
return false
}
})
Change the second example with this
this.setOnEditorActionListener(object : TextView.OnEditorActionListener {
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
return true;
}
})
Basically, you have wrong arguments type for v and event both v & event are nullable.
Related
I have an EditText that I overrode in order to detect clicks on the compound drawables.
class MyEditText
constructor(
context: Context,
attrs: AttributeSet
) : androidx.appcompat.widget.AppCompatEditText( context, attrs) {
override fun onTouchEvent(event: MotionEvent?): Boolean {
val result = super.onTouchEvent(event)
if (event != null) {
// All advice says to use ACTION_UP but that never gets here
if( event.action == MotionEvent.ACTION_DOWN ){
if(event.x <= this.totalPaddingStart){
onStartDrawableClick()
}
if(event.x >= this.width - this.totalPaddingEnd){
onEndDrawableClick()
}
performClick()
return true
}
}
return result
}
// Overriding this avoids an accessibility warning
override fun performClick(): Boolean {
return super.performClick()
}
public fun onEndDrawableClick(){
Log.e(TAG, "onEndDrawableClick: ")
}
public fun onStartDrawableClick(){
Log.e(TAG, "onStartDrawableClick: ")
}
}
This works but I want to be able to define what happens in onEndDrawableClick() from the MyEditText object instance, not in the class. I cant pass a closure to the constructor since its a view with params for xml instantiation. Is there an elegant way to do this?
(Extra bonus points for figuring out why ACTION_UP is never seen)
You can define callback properties that can be set from outside the class. Also, you can make the MotionEvent parameter non-nullable since the Android function will never pass you a null value. Then you don't have to do the null check.
Also, if you don't want other click events to happen (like if you set a click listener on the TextView) when you click on this item, you should not call super when the icon is clicked. And you should call through when the touch misses the icon instead of returning true. Example of rearranging the logic like this below.
class MyEditText(
context: Context,
attrs: AttributeSet
) : androidx.appcompat.widget.AppCompatEditText(context, attrs) {
var onEndDrawableClick: (()->Unit)? = null
var onStartDrawableClick: (()->Unit)? = null
override fun onTouchEvent(event: MotionEvent): Boolean {
if( event.action == MotionEvent.ACTION_DOWN ){
if(event.x <= this.totalPaddingStart){
onStartDrawableClick?.invoke()
performClick()
return true
}
if(event.x >= this.width - this.totalPaddingEnd){
onEndDrawableClick?.invoke()
performClick()
return true
}
}
return super.onTouchEvent(event)
}
// Overriding this avoids an accessibility warning
override fun performClick(): Boolean {
return super.performClick()
}
}
Handling a click that behaves as users are probably accustomed, i.e. has visual/audible feedback on touch down, is cancellable by moving your finger off the target before releasing, and firing only if released over the touch target; is not trivial. You might want to build this UI component out of a RelativeLayout or ConstraintLayout that contains an EditText and two Buttons in it to provide a nicer experience.
Apologies for my rusty Kotlin, but something like this should work:
var endClickHandler: View.OnClickListener?
public fun onEndDrawableClick(){
Log.e(TAG, "onEndDrawableClick: ")
endClickHandler?.onClick(this)
}
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 get rid of a warning where Android Studio wants my onTouchListener to override performClick which I do, but the warning remains.
draggableBar!!.setOnTouchListener(View.OnTouchListener { view, motionEvent ->
when (motionEvent.getAction()) {
MotionEvent.ACTION_DOWN -> {
}
MotionEvent.ACTION_UP -> {
view.performClick()
}
}
return#OnTouchListener true
})
Could this be an Android Studio bug or am I doing something wrong?
Okay, I have the same problem but i fixed it by overriding the onTouch listener.
The default onTouch wants us to override performClick(), but this does not work even calling the method by view.performClick().
So therefore override your onTouch like this:
override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
when (view) {
draggableBar -> {
when (motionEvent.getAction()) {
MotionEvent.ACTION_DOWN -> {
}
MotionEvent.ACTION_UP -> {
view.performClick()
}
}
}
otherButtonHere -> {
//your welcome
}
}
return true
}
And in that way, you can use single onTouch() in all clickable views you have.
Don't forget to implement to your Class:
View.OnTouchListener
And set the listener:
draggableBar!!.setOnTouchListener(this)
HOPE IT HELPS! :)
I am trying to use the done button of the soft keyboard to activate a method via databinding. Just like onClick. Is there a way to do that?
example:
<EditText
android:id="#+id/preSignUpPg2EnterPhone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
onOkInSoftKeyboard="#{(v) -> viewModel.someMethod()}"
/>
onOkInSoftKeyboard doesn't exists... Is there something to create this behavior?
Thanks!
I won't claim to be an expert in onEditorAction() or soft keyboard. That said, assuming you use the solution to the stack overflow question Firoz Memon suggested, you can make it happen. Even if there is another solution that works better, this can give you an idea on how to add your own event handlers.
You'd need a binding adapter that takes some kind of handler. Let's assume you have an empty listener like this:
public class OnOkInSoftKeyboardListener {
void onOkInSoftKeyboard();
}
Then you need a BindingAdapter:
#BindingAdapter("onOkInSoftKeyboard") // I like it to match the listener method name
public static void setOnOkInSoftKeyboardListener(TextView view,
final OnOkInSoftKeyboardListener listener) {
if (listener == null) {
view.setOnEditorActionListener(null);
} else {
view.setOnEditorActionListener(new OnEditorActionListener() {
#Override
public void onEditorAction(TextView v, int actionId, KeyEvent event) {
// ... solution to receiving event
if (somethingOrOther) {
listener.onOkInSoftKeyboard();
}
}
});
}
}
Using Kotlin, kapt produces:
e: [kapt] An exception occurred: android.databinding.tool.util.LoggedErrorException: Found data binding errors.
****/ data binding error ****msg:Listener class kotlin.jvm.functions.Function1 with method invoke did not match signature of any method viewModel::signIn
(because viewModel::signIn is of type KFunction1) so we can't use a method reference. However, if we create a variable within the viewModel that is explicit about the type, then we can pass that variable as the binding's param. (or just use a class)
Bindings.kt:
#BindingAdapter("onEditorEnterAction")
fun EditText.onEditorEnterAction(f: Function1<String, Unit>?) {
if (f == null) setOnEditorActionListener(null)
else setOnEditorActionListener { v, actionId, event ->
val imeAction = when (actionId) {
EditorInfo.IME_ACTION_DONE,
EditorInfo.IME_ACTION_SEND,
EditorInfo.IME_ACTION_GO -> true
else -> false
}
val keydownEvent = event?.keyCode == KeyEvent.KEYCODE_ENTER
&& event.action == KeyEvent.ACTION_DOWN
if (imeAction or keydownEvent)
true.also { f(v.editableText.toString()) }
else false
}
}
MyViewModel.kt:
fun signIn(password: String) {
Toast.makeText(context, password, Toast.LENGTH_SHORT).show()
}
val signIn: Function1<String, Unit> = this::signIn
layout.xml:
<EditText
android:id="#+id/password"
app:onEditorEnterAction="#{viewModel.signIn}"
android:imeOptions="actionDone|actionSend|actionGo"
android:singleLine="true"/>
Just as i was looking at this myself, here a simpler version where the function is directly called from the data binding:
In your ViewModel use this function:
public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
return false; // if you want the default action of the actionNext or so on
return true; // if you want to intercept
}
And in the layout:
android:onEditorAction="#{(view,actionId,event) -> viewModel.onEditorAction(view,actionId,event)}"
Kotlin, without writing custom binding adapter
In Layout,
<EditText
...
android:onEditorAction="#{(view, actionId, event) -> viewModel.onDoneClicked(view, actionId, event)}" />
ViewModel
fun onDoneClicked(view: View, actionId: Int, event: KeyEvent?): Boolean {
if(actionId == EditorInfo.IME_ACTION_DONE) {
// handle here
return true
}
return false
}
Note: event can be null, so make KeyEvent nullable by putting ? there.
You can directly call login method what inside ViewModel by implementing setOnEditorActionListener to the Edittext, taking reference from binging class
loginFragmentBinding.etPassword.setOnEditorActionListener(TextView.OnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
loginViewModel.login()
return#OnEditorActionListener true
}
false
})
Android framework already have this implemented. Take a look at TextViewBindingAdapter
You'll see those attributes, the documentation kind of glosses over what this means, but in a nutshell:
attribute = when this attribute appears in a layout file
type = then look for the implementation in this class
method = of a method with this name in the class defined in type
For more on this take a look at this blog post.