Intercept ACTION_DOWN and ACTION_UP but still send to children views - android

I'm extending LinearLayout and overriding onInterceptTouchEvent and onTouchEvent because I want to receive all touch event on all children views. I'm only interested in receiving the ACTION_DOWN and ACTION_UP MotionEvents. I still want the children view to receive all the events.
The problem I found is that if
Return false from onInterceptTouchEvent: all subsequent MotionEvents are sent to the children views and I will not receive ACTION_UP.
Return true from onInterceptTouchEvent: all subsequent MotionEvents are sent to my onTouchEvent view and the children views will not receive any.
class LinearLayout : android.widget.LinearLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet)
constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int) : super(context, attributeSet, defStyleAttr)
constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attributeSet, defStyleAttr, defStyleRes)
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
Log.d("LinearLayout", "onInterceptTouchEvent >> $event")
return when (event.action) {
MotionEvent.ACTION_DOWN -> true // or false
MotionEvent.ACTION_UP -> true
else -> false
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
Log.d("LinearLayout", "onTouchEvent >> $event")
return true
}
}
Is there any way to intercept all MotionEvents and send them all to the children views? If not, is there any other way to intercept all touch events over a ViewGroup?

Related

Using OnStateChanged listener simultaneously with OnTouchListener

I am implementing a rotary knob based on this library.
This View comes with a onStateChanged listener that tells me the current position of the knob.
val knob = findViewById<View>(R.id.knob) as Knob
knob.setOnStateChanged(object: Knob.OnStateChanged{
override fun onState(state: Int) {
// do stuff
}
})
In addition to that, I want to know when the user is no longer pressing/holding the knob (similar to a button release). I tried to achieve this with a onTouch listener.
knob.setOnTouchListener(object: View.OnTouchListener{
override fun onTouch(view: View?, event: MotionEvent?): Boolean {
// do stuff
}
})
Problem: When I add a second onTouch listener, it is no longer possible to hold and rotate the view for some reason. I do not know whether this is a problem of this particular library or Android in general.
Any suggestions on how to implement the wanted features?
A view can only have a single OnTouchListener. Unfortunately, the author of that library implemented some of its functionality using an OnTouchListener, which prevents users from using an OnTouchListener.
An alternative for you would be to subclass Knob and override onMotionEvent, like this:
class MyKnob: Knob {
constructor(context: Context): super(context)
constructor(context: Context, attrs: AttributeSet): super(context, attrs)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int): super(context, attrs, defStyleAttr)
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int): super(context, attrs, defStyleAttr, defStyleRes)
override fun onTouchEvent(event: MotionEvent): Boolean {
val result = super.onTouchEvent(event)
if (event.action == MotionEvent.ACTION_UP) {
// User released knob
}
return result
}
}
Then you'd need to use this class in your layout instead of the original Knob class.

How to detect soft-keyboard pressed when talkback is on

When talkback is on. It seems like there is no way to detect soft-keyboard pressed
I tried to use both TextView.onKeyPreIme and TextView.setOnEditorActionListener but no luck.
Do you guys have any idea how to archive this?
class MyEditText #JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : EditText(context, attrs, defStyleAttr) {
override fun onKeyPreIme(keyCode: Int, event: KeyEvent?): Boolean {
interactor?.onSoftKeyBoardPressed(keyCode, event)
return super.onKeyPreIme(keyCode, event)
}
init {
setOnEditorActionListener { v, actionId, event ->
log("Hi there", actionId.toString())
false
}
}}

Intercept touch event in RecyclerView while ignoring EditText inside

I have recyclerView with and item as seen below:
Now I want to be able to click anywhere on the item and editText should come into focus.
I can do that by setting onTouchListener on my view like this:
row_item.setOnTouchListener{ _, _ ->
editText.requestFocus()
view.background = Color.GREEN.toDrawable()
true
}
I also want to run some additional code whenever the item is clicked. Here I'm putting background color change for the sake of the example.
The problem is that whenever I click editText itself it is getting focused, but row_item touchListener is ignored, and the background doesn't change its color.
From my research, I've found that I should somehow intercept touch event. I thought I can do that by returning true in row_item.setOnTouchListener, but it doesn't work as you can see.
How can I intercept such touch event?
You should create a custom container class and then override onInterceptTouchEvent method and do your stuff there, then using this custom container class as the root of the item. Look at the following code:
class CustomFrameLayout : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
private var mOnInterceptTouchEventListener: OnTouchListener? = null
fun setOnInterceptTouchEventListener(onInterceptTouchEventListener: OnTouchListener) {
this.mOnInterceptTouchEventListener = onInterceptTouchEventListener
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
if(mOnInterceptTouchEventListener != null && mOnInterceptTouchEventListener?.onTouch(this, ev) == true)
return true;
return super.onInterceptTouchEvent(ev)
}
}
And then adding this listener to your row_item
row_item.setOnInterceptTouchEventListener(object: View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
editText.requestFocus()
v?.background = Color.GREEN.toDrawable()
return false
}
})
it may not be the best answer but it works.

RecyclerView scrolls only on specific view touch

I wonder how I can achieve such behaviour of the RecyclerView, that I could scroll the list only when I click and drag only specific view within the ViewHolder?
I've disabled horizontal scroll by creating my own LinearLayoutManager:
class MyOwnLayoutManager : LinearLayoutManager {
constructor(context: Context?) : super(context)
constructor(context: Context?, orientation: Int, reverseLayout: Boolean) : super(context, orientation, reverseLayout)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
private var isScrollEnabled = false
fun setScrollEnabled(isEnabled: Boolean) {
isScrollEnabled = isEnabled
}
override fun canScrollVertically(): Boolean {
return isScrollEnabled && super.canScrollVertically()
}
override fun canScrollHorizontally(): Boolean {
return isScrollEnabled && super.canScrollHorizontally()
}
}
Then, I try to change the isScrollEnabled by setting the touch listener to the item's header:
item_header.setOnTouchListener { v, event ->
val isScrolling = event.action == ACTION_MOVE
onHeaderIsDragging.invoke(isScrolling)
false
}
Callback in the fragment that changes adapters' layout manager var:
private val onHeaderIsDragging: ((Boolean) -> Unit) = {
recyclerViewLayoutManager.setScrollEnabled(it)
}
By this implementation, I get MOTION_CANCEL after a couple of MOTION_MOVE events in the onTouchListener and RecyclerView is not scrollable after MOTION_MOVE events.

Slight lag using button setOnTouchListener

I am working on a project that requires the exact moment a button was touched to be recorded. For this, I am using setOnTouchListener.
I have simplified the listener down to a simple print statement. Within Logcat, there is a very slight delay between when the button is touched, and when "TOUCHED" gets printed.
class MainActivity : AppCompatActivity() {
private lateinit var mp: MediaPlayer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tapButton.setOnTouchListener { v, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
println("ACTION_DOWN")
mp = MediaPlayer.create (this, R.raw.blip)
mp.start()
Toast.makeText(this#MainActivity, "ACTION_DOWN", Toast.LENGTH_SHORT).show()
background.setBackgroundColor(Color.parseColor("#ff0000"))
v.isPressed = true
} else if (event.action == MotionEvent.ACTION_UP) {
println("ACTION_UP")
background.setBackgroundColor(Color.parseColor("#ffffff"))
v.isPressed = false
}
true
}
}
}
XML (as basic as it can get!)
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:text="Button"
android:layout_width="188dp"
android:layout_height="288dp"
android:id="#+id/tapButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>
How can I prevent this lag, so that I can get the precise moment the button was touched?
If it requires not using a button, and using a tappable view - that is fine too. It's imperative that this lag is removed. Additionally - if this lag is something that can't be addressed - perhaps I can offset the UI if I have the exact length (in milliseconds) that this lag occurs for. Open to ideas.
EDIT:
I believe I found the solution here; just don't know how to fix it.
How can I make a Button more responsive?
This thread states there's something called 'getTapTimeout' that intentionally puts a delay on a touch event to determine if it will be a tap or scroll. THE CULPRIT?!
How do I set this to 0??
Don't use logcat to tell. Logcat sends data to a separate process. It can be used for basic timings, but not for ms exact timings.
Every MotionEvent has a timestamp in it. You can get it with motionEvent.getEventTime(). Use that for your timing data.
You could create your own CustomView that extends View and overrides dispatchTouchEvent. You'll need to do something along the following lines,
interface CustomListener {
fun customOnTouch()
}
class CustomTouchView : View {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes)
private var customOnTouchListener:CustomListener? = null
public fun setCustomOnTouchListener(listener: CustomListener) {
this.customOnTouchListener = listener
}
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
customOnTouchListener?.customOnTouch()
return super.dispatchTouchEvent(event)
}
}

Categories

Resources