Why is MotionEvent.getx() not the same result in onTouchEvent and onDraw? - android

private lateinit var lastEvent: MotionEvent
override fun onDraw(canvas: Canvas?) {
...
Log.d("demo","ondraw lastEvent.x="+lastEvent.x+" lastEvent.y="+lastEvent.y)
Log.d("demo","ondraw lastEvent.rawX="+lastEvent.rawX+" lastEvent.rawY="+lastEvent.rawY)
...
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
...
when (event.action) {
MotionEvent.ACTION_DOWN -> {
return true
}
MotionEvent.ACTION_MOVE -> {
lastEvent = event
Log.d("demo","onTouchEvent lastEvent.x="+lastEvent.x+" lastEvent.y="+lastEvent.y)
}
else -> {
}
}
...
}
Why the lastEvent.x get different results。This problem is bothering me.
Place help me, thanks.

Most probably, the method onDraw() is not being called as soon as you touch the view.
Add a call to invalidate() at the end of the method onTouchEvent() so that it will invoke the method onDraw() internally and draw at the same location.

Related

Motion Event - press duration

How can I get a hold time for an item? I would like to show dialog not on every touch but on a continuous touch for example 2s. Something similar to Long Click.
My listener:
holder.itemView.setOnTouchListener(object: View.OnTouchListener{
val fragment = PodgladFragment()
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> {
fragment.show((context as FragmentActivity).supportFragmentManager, "Dialog")
}
MotionEvent.ACTION_UP -> {
if(fragment.isAdded)
fragment.dismiss()
}
}
return true
}
})

GestureDetector not registering double taps

I am having trouble implementing a touchListener to my recyclerview items that listens for Single taps and double taps.
Here is my code:
val gDetector = GestureDetector(root.context, object : SimpleOnGestureListener() {
override fun onDoubleTap(e: MotionEvent?): Boolean {
Log.i(TAG,"Double tapped")
return super.onDoubleTap(e)
}
override fun onShowPress(e: MotionEvent?) {
Log.i(TAG,"Show Press")
super.onShowPress(e)
}
override fun onSingleTapUp(e: MotionEvent?): Boolean {
Log.i(TAG,"Single tap up")
return super.onSingleTapUp(e)
}
override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
Log.i(TAG,"Double tapped event")
return super.onDoubleTapEvent(e)
}
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
Log.i(TAG,"single tap confirmed")
return super.onSingleTapConfirmed(e)
}
override fun onLongPress(e: MotionEvent?) {
Log.i(TAG,"Long press")
super.onLongPress(e)
}
})
root.setOnTouchListener { view, event ->
gDetector.onTouchEvent(event)
}
I am using GestureDetector with onTouchListener but only onShowPress and onLongPress get called when I test it out. onLongPress gets called even when I do a quick tap on an item... onShowPress gets called everytime I tap no matter what.
I was unable to find examples online that combine both GestureDetector with onTouchListener on a view with Kotlin.
any ideas on how to get onDoubleTap and onSingleTapConfirmed to work?
tried removing supers and returning true (this does not work)
I am trying to implement 'GestureDetector.onDoubleTapListener' but I am getting this error:
Try delete super methods. And return false where you don't need to register event.
Return true only where you need to register that event happened.

How to dismiss Bottom Sheet fragment when click outside in Kotlin?

I make bottom sheet fragment like this:
val bottomSheet = PictureBottomSheetFragment(fragment)
bottomSheet.isCancelable = true
bottomSheet.setListener(pictureListener)
bottomSheet.show(ac.supportFragmentManager, "PictureBottomSheetFragment")
But its not dismiss when I touch outside. and dismiss or isCancelable not working.
try this
behavior.setState(BottomSheetBehavior.STATE_HIDDEN));
You can override method and indicate, for example, in onViewCreated what you need:
class ModalDialogSuccsesDataPatient : ModalDialog() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
isCancelable = false //or true
}
}
Let's try to design reusable functions to solve this problem and similar ones if the need arises.
We can create extension functions on View that tell whether a point on the screen is contained within the View or not.
fun View.containsPoint(rawX: Int, rawY: Int): Boolean {
val rect = Rect()
this.getGlobalVisibleRect(rect)
return rect.contains(rawX, rawY)
}
fun View.doesNotContainPoint(rawX: Int, rawY: Int) = !containsPoint(rawX, rawY)
Now we can override the dispatchTouchEvent(event: MotionEvent) method of Activity to know where exactly the user clicked on the screen.
private const val SCROLL_THRESHOLD = 10F // To filter out scroll gestures from clicks
private var downX = 0F
private var downY = 0F
private var isClick = false
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
when (event.action and MotionEvent.ACTION_MASK) {
MotionEvent.ACTION_DOWN -> {
downX = event.x
downY = event.y
isClick = true
}
MotionEvent.ACTION_MOVE -> {
val xThreshCrossed = abs(downX - event.x) > SCROLL_THRESHOLD
val yThreshCrossed = abs(downY - event.y) > SCROLL_THRESHOLD
if (isClick and (xThreshCrossed or yThreshCrossed)) {
isClick = false
}
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
if (isClick) onScreenClick(event.rawX, event.rawY)
}
else -> { }
}
return super.dispatchTouchEvent(event)
}
private fun onScreenClick(rawX: Float, rawY: Float) { }
Now, you can simply use the above-defined functions to achieve the required result
private fun onScreenClick(rawX: Float, rawY: Float) {
if (bottomSheet.doesNotContainPoint(rawX.toInt(), rawY.toInt())) {
// Handle bottomSheet state changes
}
}
What more? If you have a BaseActivity which is extended by all your Activities then you can add the click detection code to it. You can make the onScreenClick an protected open method so that it can be overridden by the sub-classes.
protected open fun onScreenClick(rawX: Float, rawY: Float) { }
Usage:
override fun onScreenClick(rawX: Float, rawY: Float) {
super.onScreenClick(rawX, rawY)
if (bottomSheet.doesNotContainPoint(rawX.toInt(), rawY.toInt())) {
// Handle bottomSheet state changes
}
}

Android VIewGroup handle touch, child handle click

I have i custom ViewPager that detects taps, long press and long press up events with a GestureDetector. It also allows to wipe ViewPager.
Gesture listener is simple:
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent?) = true
override fun onLongPress(e: MotionEvent?) {
mTouchListener.invoke(TAPEVENT.LONGTAP)
mWasLongTap = true
}
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
mTouchListener.invoke(TAPEVENT.TAP)
return true
}
override fun onSingleTapUp(e: MotionEvent?): Boolean {
if (mWasLongTap) {
mWasLongTap = false
mTouchListener.invoke(TAPEVENT.LONGTAPUP)
}
return true
}
}
//override view group methods
override fun onTouchEvent(ev: MotionEvent?): Boolean {
super.onTouchEvent(ev)
return mDetector.onTouchEvent(ev)
}
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
mDetector.onTouchEvent(ev)
return super.dispatchTouchEvent(ev)
}
And i have fragments inside a ViewPager. In my fragments i have, lets say, a Button view.
When my button is clicked onSingleTapConfirmed event is fired too.
Cannot figure out what to do to force ViewPager do not process event if there was a click on a child view of a fragment.
Fragment view looks something like this:
<CoordinatorLayout>
<View>
</View>
<Button>
</Button>
</CoordinatorLayout>
I ended up with such a solution.
GestureListener is the same, but onTouchEvent is overridden the other way.
override fun onTouchEvent(ev: MotionEvent): Boolean {
if (ev.action == MotionEvent.ACTION_UP && mWasLongTap) {
mWasLongTap = false
mTouchListener.invoke(TAPEVENT.LONGTAPUP)
}
return mDetector.onTouchEvent(ev) || super.onTouchEvent(ev)
}

Android Kotlin child onClick blocks parent OnTouch

I have this layout hierarchy:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/parent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.xxxxxx.Widget
android:id="#+id/widget1"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<com.xxxxxx.Widget
android:id="#+id/widget2"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</LinearLayout>
I have touch Events for the Parent LinearLayout like this:
parent.setOnTouchListener(myCustomTouchParent)
class MyCustomTouchParent(ctx: Context): View.OnTouchListener {
private var isScrollingDown = false
private var isScrollingUp = false
private val myGestureDetected = GestureDetector(ctx, MyGestureListener())
var onRecyclerViewMovingDown: (() -> Unit)? = null
override fun onTouch(p0: View?, e: MotionEvent): Boolean {
myGestureDetected.onTouchEvent(e)
when(e.action){
MotionEvent.ACTION_UP -> {
if (isScrollingDown) {
onRecyclerViewMovingDown?.invoke()
}
}
MotionEvent.ACTION_DOWN -> {
Log.i("TAg", "Action Down")
isScrollingDown = false
isScrollingUp = false
}
}
return true
}
inner class MyGestureListener: GestureDetector.SimpleOnGestureListener() {
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
if(e2.y - e1.y > 0){
isScrollingUp = true
} else if(e2.y - e1.y < 0){
isScrollingDown = true
}
return super.onScroll(e1, e2, distanceX, distanceY)
}
}
}
Basically, this will detect a 'Scroll Up' event on the parent, and will perform some animations. The problem is, as soon as I set a click listener for widget1 and widget2, the touch event of the parent is no longer working. Is there any workaround for this?
The only thing that worked for me: In the parent LinearLayout, intercept the touch, call onTouchEvent and return false:
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
onTouchEvent(ev)
return false
}
Move the TouchInterceptor with the Gesture Detector class to the parent onTouchEvent:
override fun onTouchEvent(e: MotionEvent): Boolean {
myGestureDetected.onTouchEvent(e)
when(e.action){
MotionEvent.ACTION_UP -> {
if (isScrollingDown) {
onRecyclerViewMovingDown?.invoke()
}
}
MotionEvent.ACTION_DOWN -> {
isScrollingDown = false
isScrollingUp = false
}
}
return super.onTouchEvent(e)
}
I don't know if there is a better solution, but this one let me handle touch event on the parent first, then it passed the touch to the childs. There you can set your click listeners.
Also, if you don't set click listeners, the area of touch that contains the clickable item won't trigger touch. so, better set
clickable=true
in all the items, and then only set listeners when you need.
You have to override both onTouchListeners on your children views and return false, that will make them not override their parent ontouch.
widget1.onTouch { view, motionEvent -> return#onTouch false }
widget2.onTouch { view, motionEvent -> return#onTouch false }

Categories

Resources