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)
}
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
}
})
In my MainActivity i have:
class MainActivity : AppCompatActivity() {
lateinit var imageView: ImageView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val imageViewGestureDetector = GestureDetector( this, object : GestureDetector.SimpleOnGestureListener() {
override fun onDoubleTap(e: MotionEvent?): Boolean {
return true
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
Log.i("MainActivity", "Fling!")
return super.onFling(e1, e2, velocityX, velocityY)
}
})
imageView.setOnTouchListener { _, event -> imageViewGestureDetector.onTouchEvent(event) }
}
}
Now I thought it might be nice to define a behaviour for onScale but I noticed Scale Gestures are detected by the ScaleGestureDetector.
I'd rather not sub-class the ImageView and override fun onTouchEvent() - or should I? That's what all of the solutions in the net seem to suggest. I'd assume there might be a convenient way to combine these common gestures in one view.
I found a quite simple way myself.
I wrote a ScaleGestureDetector like that
val imageViewScaleGestureDetector = ScaleGestureDetector(this, object: ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector?): Boolean {
Log.i(“MainActivity”, “scale”)
return super.onScale(detector)
}
})
and changed the line
imageView.setOnTouchListener { _, event -> imageViewGestureDetector.onTouchEvent(event) }
to
imageView.setOnTouchListener { _, event ->
imageViewScaleGestureDetector.onTouchEvent(event)
imageViewGestureDetector.onTouchEvent(event)
}
My ImageView is detecting both fling and scale gesture. Let me know if that's bad practice or there are better solutions.
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.
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 }
I am currently working with recyclerView and cards.
I have a cards layout. Now i want to add scrollview inside the cards is that possible. If yes then how ?
The brown layout is the recycler view layout while the blue layout is the cards layout. Inside each card i need a scrollview is that possible ?
I am not sure if this will work but you can try this
Disable the touch of recycler view when scrollview is being touched.
Similarly disable scrollview touch on recyclerview touch.
This can be acheived like this
//For scrollView
scrollView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
// Disallow the touch request for parent scroll on touch of child view
view.getParent().requestDisallowInterceptTouchEvent(false);
return true;
}
});
see if this helps you.
I finally found an another way how the ScrollView can be scrolled inside RecyclerView viewHolder. As Ankit Khare's answer, in order to this works, we should disable the touch of RecyclerView when ScrollView is being touched, but his solution is too simple and it didn't quite work well after I tried that. Yeah I know both question and answers were posted from 6 years ago, maybe at that time it worked, but now it's not work anymore after I tried that.
Here is my analysis. Instead of disable the touch of RecyclerView, we should disable its scroll mechanism instead. In order to RecyclerView's scroll can be disabled, we should use function canScrollVertically() from LayoutManager which you use for the RecyclerView. Then, in RecyclerView's adapter, I implemented onTouchListener() for the ScrollView to trigger the touch on it and disable or enable the RecyclerView. So, here is my sample code (I'm using Kotlin):
class MyActivity : AppCompatActivity(), MyAdapter.AdapterListener {
private var isVerticalScrollEnabled = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/* Set RecyclerView adapter here */
recyclerView.apply {
layoutManager = object : LinearLayoutManager(this#MyActivity) {
override fun canScrollVertically(): Boolean {
return isVerticalScrollEnabled
}
}
adapter = MyAdapter(callback = this#MyActivity)
}
}
/**
* Callback from MyAdapter.AdapterListener.
*/
override fun onScrollViewTouched(isTouched: Boolean) {
isVerticalScrollEnabled = !isTouched
}
}
class MyAdapter internal constructor(private val callback: AdapterListener) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, i: Int) {
/* set touch listener for scrollview */
scrollView.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
return when (event?.action) {
MotionEvent.ACTION_DOWN -> {
callback.onScrollViewTouched(isTouched = true)
true
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
callback.onScrollViewTouched(isTouched = false)
true
}
else -> {
/* return false so that scrollview can scroll the content */
false
}
}
}
})
}
internal interface AdapterListener {
fun onScrollViewTouched(isTouched: Boolean)
}
}
Hope this will help on someone who look up another answer.
Try this instead.
public class NoInterceptScrollView extends ScrollView {
public NoInterceptScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
}