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 }
Related
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
}
})
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.
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.
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)
}
In my case I want to open PopupWindow by long press on ViewHolder item and process motion event in this window without removing finger. How can I achieve this?
I trying to open CustomPopupWindow by follow:
override fun onBindViewHolder(holder: Item, position: Int) {
val item = items[position]
holder.bindView(testItem)
holder.itemView.view.setOnLongClickListener {
val inflater = LayoutInflater.from(parent?.context)
val view = inflater.inflate(R.layout.popup_window, null)
val popupMenu = CustomPopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
popupMenu.elevation = 5f
popupMenu.showAsDropDown(holder.itemView.view)
true
}
}
and after that disable scrolling in RecyclerView:
class CustomLayoutManager(context: Context) : LinearLayoutManager(context) {
var scrollEnabled: Boolean = true
override fun canScrollVertically(): Boolean {
return scrollEnabled
}
}
Here my CustomPopupWindow:
class CustomPopupWindow(contentView: View?, width: Int, height: Int) : PopupWindow(contentView, width, height), View.OnTouchListener {
init {
contentView?.setOnTouchListener(this)
setTouchInterceptor(this)
}
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
Log.i("Touch", "Touch")
}
MotionEvent.ACTION_MOVE -> {
Log.i("Touch", "Event {${event.x}; ${event.y}}")
}
MotionEvent.ACTION_UP-> {
Log.i("Touch", "Up")
}
}
return true
}
}
In this case onTouch() event never called in CustomPopupWindow only if I remove finger and tap again.
Thanks advance!
SOLVED
I solved this by adding a touch listener to the anchor view:
holder.itemView.view.setOnLongClickListener {
val inflater = LayoutInflater.from(parent?.context)
val view = inflater.inflate(R.layout.popup_window, null)
val popupMenu = CustomPopupWindow(view, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
popupMenu.elevation = 5f
it.setOnTouchListener(popupMenu) // solution
popupMenu.showAsDropDown(it)
true
}
Thanks #Brucelet
If you can refactor to using a PopupMenu, then I think PopupMenu.getDragToOpenListener() will do what you want. Similar for ListPopupWindow.createDragToOpenListener().
You could also look at the implementation of those methods for inspiration in creating your own.