I have a recyclerView along with a tabLayout. when user navigates through tabs, the recyclerView is loaded with new items. What I want now, is add the swipe feature so when the user swipes to right or left on the recyclerView, the app switches between tabs (and reload recyclerView with new Items). I have had a tough time trying to efficiently detect swipe (right/left) gestures on a recyclerView. I don't want to use ViewPager because I want to keep my app light and fast. besides the only feature I want, is only swipe. So here is the last method I have used:
OnSwipeTouchListener onSwipeTouchListener = new OnSwipeTouchListener(getActivity())
    {
        public void onSwipeRight() {
            //update recyclerview with new data
        }
        public void onSwipeLeft(){
            //update recyclerview with new data
        }
    };
b.rcvItems.setOnTouchListener(onSwipeTouchListener);
b.rcvItems.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if (dy > 0) {
                int threshold = 20;
                int count = adapter.getItemCount();
                LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
                int lastVisible = layoutManager.findLastVisibleItemPosition();
                if (lastVisible >= count - threshold) { 
                    //load more items
                }
            }
        }
    });
And here is the class OnSwipeTouchListener within which is the class GestureListener:
public class OnSwipeTouchListener implements View.OnTouchListener {
private final GestureDetector gestureDetector;
protected OnSwipeTouchListener(Context ctx) {
    gestureDetector = new GestureDetector(ctx, new GestureListener());
}
@Override
public boolean onTouch(View v, MotionEvent event) {
    return gestureDetector.onTouchEvent(event);
}
public void onSwipeRight() {
}
public void onSwipeLeft() {
}
public void onSwipeTop() {
}
public void onSwipeBottom() {
}
private final class GestureListener extends GestureDetector.SimpleOnGestureListener {
    private static final int SWIPE_THRESHOLD = 23;
    private static final int SWIPE_VELOCITY_THRESHOLD = 0;
    @Override
    public boolean onDown(MotionEvent e) {
        return true;
    }
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        boolean result = false;
        try {
            float diffY = e2.getY() - e1.getY();
            float diffX = e2.getX() - e1.getX();
            if (Math.abs(diffX) > Math.abs(diffY)) {
                if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
                    if (diffX > 0) {
                        onSwipeRight();
                    } else {
                        onSwipeLeft();
                    }
                    result = true;
                }
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return result;
    }
}
}
Now the problem is when the Fragment is loaded, in the first place, the swipe feature doesn't work. But after I scroll the recyclerView, or sometimes after I try to swipe many times, it starts to detect the swipes! And then it goes soft and smooth. I checked and noticed that the first parameter MotionEvent e1 in the method onFling() is null until I first scroll my recyclerView or after many swipe attempts. Still at some launches, the swipe feature works fine even in the very first place! (It's rare but it happens). It's so hacky and I can't even figure out the right behavior. How can I fix the issue?
Side note: Sometime the app freezes and then crashes. And I get the following crash log:
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionMyViewHolder{779d2db position=27 id=-1, oldPos=-1, pLpos:-1 no parent} androidx.recyclerview.widget.RecyclerView{2415051 VFED..... ........ 0,0-1080,2049 #7f09016d app:id/rcvItems}, adapter:ui.adapter.ItemsAdapter@bee10b6, layout:androidx.recyclerview.widget.LinearLayoutManager@38c1b7, context:ui.MainActivity@577a61c
    at androidx.recyclerview.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:5972)
    at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6156)
    at androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:288)
    at androidx.recyclerview.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:345)
    at androidx.recyclerview.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:361)
    at androidx.recyclerview.widget.GapWorker.prefetch(GapWorker.java:368)
    at androidx.recyclerview.widget.GapWorker.run(GapWorker.java:399)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:237)
    at android.app.ActivityThread.main(ActivityThread.java:8167)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:496)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1100)
import android.content.Context
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import kotlin.math.abs
internal open class OnSwipeTouchListener(c: Context?) :
OnTouchListener {
   private val gestureDetector: GestureDetector
   override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
      return gestureDetector.onTouchEvent(motionEvent)
   }
   private inner class GestureListener : SimpleOnGestureListener() {
      private val SWIPE_THRESHOLD: Int = 100
      private val SWIPE_VELOCITY_THRESHOLD: Int = 100
      override fun onDown(e: MotionEvent): Boolean {
         return true
      }
      override fun onSingleTapUp(e: MotionEvent): Boolean {
         onClick()
         return super.onSingleTapUp(e)
      }
      override fun onDoubleTap(e: MotionEvent): Boolean {
         onDoubleClick()
         return super.onDoubleTap(e)
      }
      override fun onLongPress(e: MotionEvent) {
         onLongClick()
         super.onLongPress(e)
      }
      override fun onFling(
         e1: MotionEvent,
         e2: MotionEvent,
         velocityX: Float,
         velocityY: Float
      ): Boolean {
         try {
            val diffY = e2.y - e1.y
            val diffX = e2.x - e1.x
            if (abs(diffX) > abs(diffY)) {
               if (abs(diffX) > SWIPE_THRESHOLD && abs(
                  velocityX
               ) > SWIPE_VELOCITY_THRESHOLD
            ) {
               if (diffX > 0) {
                  onSwipeRight()
               }
               else {
                  onSwipeLeft()
               }
            }
         }
          else {
            if (abs(diffY) > SWIPE_THRESHOLD && abs(
               velocityY
            ) > SWIPE_VELOCITY_THRESHOLD
         ) {
            if (diffY < 0) {
               onSwipeUp()
            }
            else {
                  onSwipeDown()
                  }
               }
               }
         } catch (exception: Exception) {
         exception.printStackTrace()
      }
      return false
      }
   }
   open fun onSwipeRight() {}
   open fun onSwipeLeft() {}
   open fun onSwipeUp() {}
   open fun onSwipeDown() {}
   private fun onClick() {}
   private fun onDoubleClick() {}
   private fun onLongClick() {}
   init {
      gestureDetector = GestureDetector(c, GestureListener())
   }
}
val recyclerView = findViewById(R.id.recyclerView)
recyclerView.setOnTouchListener(object : OnSwipeTouchListener(this@MainActivity) {
         override fun onSwipeLeft() {
            super.onSwipeLeft()
            Toast.makeText(this@MainActivity, "Swipe Left gesture detected",
            Toast.LENGTH_SHORT)
            .show()
         }
         override fun onSwipeRight() {
            super.onSwipeRight()
            Toast.makeText(
               this@MainActivity,
               "Swipe Right gesture detected",
               Toast.LENGTH_SHORT
            ).show()
         }
         override fun onSwipeUp() {
            super.onSwipeUp()
            Toast.makeText(this@MainActivity, "Swipe up gesture detected", Toast.LENGTH_SHORT)
            .show()
         }
         override fun onSwipeDown() {
            super.onSwipeDown()
            Toast.makeText(this@MainActivity, "Swipe down gesture detected", Toast.LENGTH_SHORT)
            .show()
         }
      })
   }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With