Scroll fixed number of items on fling/scroll to recyclerview - android

I have a simple activity which has a root layout.The root layout contains a recyclerview. Recyclerview will contain 20 items created from items.xml ,
which consists of 3 text views.Here items.xml represent single item in the recyclerview.
The recycler view can be scrolled both the ways , it is kind a loop of the items between 1 to 20.
I have a requirement to smoothscroll only 4 items , irrespective of the velocity of the swipe/fling.
Approaches which i have tried many approaches but coudn't succeed till now. If anyone can give any suggestions , that would be very helpful.
SnapHelper - this seems to snap only the item which is in the center.
Using gesture detector - the gestures are getting detected for all the actions , but i am unable to prevent the recyclerview's default scrolling behaviour.
The main issue is the over-scrolling of recycler view due to varing velocities of user scrolls and flings.
Here is my set up for the gesture detector.
public myRecyclerTouchListner(final Context context, final RecyclerView recycleView,
final LinearLayoutManager linearLayoutManager,
final ClickListener clicklistener){
final ViewConfiguration vc = ViewConfiguration.get(context);
final int swipeMinDistance = vc.getScaledPagingTouchSlop();
final int swipeThresholdVelocity = vc.getScaledMinimumFlingVelocity();
final int swipeMaxOffPath = vc.getScaledTouchSlop();
this.clicklistener=clicklistener;
simpleGestureDetector =new GestureDetector(context,new GestureDetector.SimpleOnGestureListener( ){
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.d("try", "on Fling called");
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE &&
Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
//From Right to Left
recycleView.smoothScrollToPosition(linearLayoutManager.
findLastCompletelyVisibleItemPosition() + Constants.spanLength);
return true;
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE &&
Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
//From Left to Right
recycleView.smoothScrollToPosition(linearLayoutManager.
findFirstCompletelyVisibleItemPosition() - Constants.spanLength);
return true;
}
return true;
}
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return true;
}
#Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
#Override
public boolean onDown(MotionEvent e) {
return true;
}
#Override
public boolean onDoubleTap(MotionEvent e) {
return true;
}
#Override
public void onLongPress(MotionEvent e) {
Log.d("Try", "Long press gesture");
View child=recycleView.findChildViewUnder(e.getX(),e.getY());
if(child!=null && clicklistener!=null){
clicklistener.onLongClick(child,recycleView.getChildAdapterPosition(child));
}
}
});

I think your problem is that scrolling is starting from last visible position where Fling is detected and that mess up the things.
Try with global variable for example int lastScrolledPosition = 0;.
And OnFling recycleView.smoothScrollToPosition(lastScrolledPosition + Constants.spanLength);
I thing this should do the trick. You should change if lastScrolledPosition < Recycleritems count.

It turns out I had to use Snaphelper for custom scroll behavior in case wherein I need control over programmatic scroll and user-scroll(fling).
This was exactly what I was looking for:
How to snap RecyclerView items so that every X items would be considered like a single unit to snap to?
I hope it will be helpful :)

Related

Kinetic translation in Y-axis using FlingAnimation

I'm trying to add 'kinetic' move in Y-axis from top of the screen to bottom (and vice versa) to custom layout displayed above all apps using the window manager.
I tried to do it in this way:
private void animateFlingInYAxis(View view) {
FlingAnimation fling = new FlingAnimation(view, DynamicAnimation.SCROLL_Y);
fling.setStartValue(0.9F);
fling.setStartVelocity(0f);
//fling.setMinValue(0.5F);
fling.setFriction(0.2F);
fling.start();
}
But the following result is no working correctly. I tried to change values SCROLL_Y to TRANSLATION_Y and set a custom range of values but without luck.
Action is processed in ACTION_MOVE event in this way:
case MotionEvent.ACTION_MOVE:
params.y = initialY + (int) (event.getRawY() - initialTouchY);
if(isMovingInYAxis((int) initialTouchY ,(int) event.getRawY())) {
windowManager.updateViewLayout(view, params);
animateFlingInYAxis(view);
}
break;
I would like to ask how to animate moving in Y-axis in the right way?
Many thanks for any advice.
Edit1: After setting setStartVelocity(2000); seems to be working better, but the view is partially hiding (see screenshot below).
Edit2: I Implemented GestureDetector.OnGestureListener in Custom View, now I am able to catch fling and scroll events. In onFling method, I am able to recognize direction of fling gesture (TOP_BOTTOM, BOTTOM_TOP), but I am not able to to do the animation of view of given direction.
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Logger.d("onFling");
if(e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
Logger.d("Bottom to top");
animateFlingInYAxis(view, "BOTTOM_TOP");
return false; // Bottom to top
} else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
Logger.d("Top to bottom");
animateFlingInYAxis(view, "TOP_BOTTOM");
return false; // Top to bottom
}
return false;
}
private void animateFlingInYAxis(View view, String type) {
FlingAnimation fling = new FlingAnimation(view, DynamicAnimation.TRANSLATION_Y);
if(type.equals("BOTTOM_TOP")) {
fling.setStartValue(100); //WHICH PARAMS TO PASS?
fling.setMinValue(480); //WHICH PARAMS TO PASS?
fling.setMaxValue(0); //WHICH PARAMS TO PASS?
} else{
fling.setStartValue(470);
fling.setMinValue(480);
fling.setMaxValue(0);
}
fling.setStartVelocity(2000);
fling.setFriction(0.8F);
fling.start();

Horizontal swipe not detected in ScrollView's parent [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Gesture detection and ScrollView issue
EDIT: question with full code asked here.
I've got a layout with a child. I set a gesture listener to detect horizontal swipe on the layout. When the layout is a LinearLayout the swipe is properly detected, but when it's a ScrollView, it's not. I guess the gesture is first detected by the ScrollView and is not propagated to its ascendants, but I don't know how to solve it.
Here's my layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical">
<ImageView android:layout_width="320dp" android:layout_height="30dp"
android:src="#drawable/header"/>
<ScrollView android:layout_width="fill_parent" android:layout_height="wrap_content">
<!-- stuff -->
</ScrollView>
</LinearLayout>
I set the following listener to my layout:
class ProductGestureListener extends SimpleOnGestureListener {
#Override
public boolean onDown(MotionEvent e) {
return true;
}
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
final int SWIPE_MIN_DISTANCE = 120;
final int SWIPE_MAX_OFF_PATH = 250;
final int SWIPE_THRESHOLD_VELOCITY = 200;
try {
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
return false;
if(e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
// show previous item
} else if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
// show next item
}
} catch (Exception e) {
}
return false;
}
}
If you want the whole Activity to be swipeable horizontally you can use the following as a super class for your Activity:
public abstract class SwipeActivity extends Activity {
private static final int SWIPE_MIN_DISTANCE = 120;
private static final int SWIPE_MAX_OFF_PATH = 250;
private static final int SWIPE_THRESHOLD_VELOCITY = 200;
private GestureDetector gestureDetector;
#Override
protected void onCreate( Bundle savedInstanceState ) {
super.onCreate( savedInstanceState );
gestureDetector = new GestureDetector( new SwipeDetector() );
}
private class SwipeDetector extends SimpleOnGestureListener {
#Override
public boolean onFling( MotionEvent e1, MotionEvent e2, float velocityX, float velocityY ) {
// Check movement along the Y-axis. If it exceeds SWIPE_MAX_OFF_PATH,
// then dismiss the swipe.
if( Math.abs( e1.getY() - e2.getY() ) > SWIPE_MAX_OFF_PATH )
return false;
// Swipe from right to left.
// The swipe needs to exceed a certain distance (SWIPE_MIN_DISTANCE)
// and a certain velocity (SWIPE_THRESHOLD_VELOCITY).
if( e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs( velocityX ) > SWIPE_THRESHOLD_VELOCITY ) {
next();
return true;
}
// Swipe from left to right.
// The swipe needs to exceed a certain distance (SWIPE_MIN_DISTANCE)
// and a certain velocity (SWIPE_THRESHOLD_VELOCITY).
if( e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs( velocityX ) > SWIPE_THRESHOLD_VELOCITY ) {
previous();
return true;
}
return false;
}
}
#Override
public boolean dispatchTouchEvent( MotionEvent ev ) {
// TouchEvent dispatcher.
if( gestureDetector != null ) {
if( gestureDetector.onTouchEvent( ev ) )
// If the gestureDetector handles the event, a swipe has been
// executed and no more needs to be done.
return true;
}
return super.dispatchTouchEvent( ev );
}
#Override
public boolean onTouchEvent( MotionEvent event ) {
return gestureDetector.onTouchEvent( event );
}
protected abstract void previous();
protected abstract void next();
}
All you need to do is implement the next() and previous() methods after extending SwipeActivity.
I had to add
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
super.dispatchTouchEvent(ev);
return productGestureDetector.onTouchEvent(ev);
}
Add this method in your activity class which uses swiper just like the onCreate method.
The sequence of events for android is this
user performs some action
-> action is passed to parent
->if parent handles action then the action is consumed
->else the action is passed to a child
->if child handles action then the action is consumed
->else the action is passed on
this process continues until the action is consumed or all children have received the action and none of them handled it.
To detect a horizontal swipe in a scroll view and pass it to a child instead of the scroll view consuming it the event needs to be intercepted.
ie
user performs some action
-> action is passed to parent
->if horizontal swipe pass to child
-> else have the scroll view handle the action
to do this (as out lined in the top answer here: HorizontalScrollView within ScrollView Touch Handling ) a gesture detector is used in the scroll view for the sole purpose of detecting whether the gesture is horizontal or vertical.
If the gesture is horizontal then we want to intercept the event and pass it to the child.
You need to create a custom scrollview then implement a gesture detector there and call it from onInterceptTouch(). By returning true or false here we can say whether or not to consume the event here, everything you need is in that link above.

Viewflipper with Scrollviews and a nav bar

I have a viewflipper which includes a number of layouts. Each layout has a scrollview as the root.
Below the viewflipper I have a horizontal scrollview which contains textviews which acts as a navigation bar.
My original issue was that the fling stopped working as soon as I added a scrollview but I added the following code:
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
super.dispatchTouchEvent(ev);
return gestureDetector.onTouchEvent(ev);
}
And the scrolling now works along with the fling.
The only issue I have now though is that the HSV nav bar now acts a little strange. Sometimes when I try to slide it across, to select new contents, the HSV springs back to where it was. Other times the HSV thinks I want to fling and moves to the next contents. This is of course dependant of the speed I try to scroll it.
I want the nav bar to work independently of the flipper.
I'm trying to understand the code to fix it myself but not getting anywhere.
I have the following in my onCreate:
gestureDetector = new GestureDetector(new MyGestureDetector());
gestureListener = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetector.onTouchEvent(event)) {
return true;
}
return false;
}};
I have the MyGestureDetector class:
class MyGestureDetector extends SimpleOnGestureListener {
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH)
return false;
// right to left swipe
if(e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
flipper.setInAnimation(flip_in_from_right);
flipper.setOutAnimation(flip_out_to_left);
flipper.showNext();
} else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
flipper.setInAnimation(flip_in_from_left);
flipper.setOutAnimation(flip_out_to_right);
flipper.showPrevious();
}
} catch (Exception e) {
// nothing
}
And then the override:
#Override
public boolean dispatchTouchEvent(MotionEvent ev){
super.dispatchTouchEvent(ev);
return gestureDetector.onTouchEvent(ev);
}
Can anyone shed any light on this?
Where exactly is your HSV? Is it pinned to the bottom of the screen? If so, here's what I would do. Within your onFling event, check the Y position of the fling and make sure it's not within your HSV. If it is, then return false and you should be good to go.

Android ListView tolerance

I have list items that have a HorizontalScrollView in each of them. Hence, I should be able to swipe up and down the list and also swipe through the list item horizontally. (e.g. Pulse News App).
What I'm encountering is that, whenever I scroll the HorizontalScrollView, the ListView also gets scrolled by tiny bit but giving out annoying disturbances in terms of User Experience.
So, I was actually thinking to subclass ListView and implement my own ListView widget. However, I don't know which method to override so that I could increase X tolerance and stop the listview from giving these unwanted flickering.
gestureDetector = new GestureDetector(new YScrollDetector());
public boolean onInterceptTouchEvent(MotionEvent ev) {
//Call super first because it does some hidden motion event handling
boolean result = super.onInterceptTouchEvent(ev);
//Now see if we are scrolling vertically with the custom gesture detector
if (gestureDetector.onTouchEvent(ev)) {
return result;
}
//If not scrolling vertically (more y than x), don't hijack the event.
else {
return false;
}
}
// Return false if we're scrolling in the x direction
class YScrollDetector extends SimpleOnGestureListener {
#Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
try {
if (Math.abs(distanceY) > Math.abs(distanceX)) {
return true;
} else {
return false;
}
} catch (Exception e) {
// nothing
}
return false;
}
}
This is will help you, you have to override these in the custom list view you create,i used this to eliminate the problem in my application. Hope this helps.

View with gesture listener and onLongClickListener

I'm trying to implement a view that has both a longClickListener and a gesture dectector. Basically, I need a button to show a view when the user long clicks on the first view, and then I want to dectect a fling motion up. I would like to make it so that the user does not have to lift their finger at all, and hit both the longclick, and the fling motion.
Here is my code for the longClickListener:
flipCard.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View view) {
answerRight.setVisibility(View.VISIBLE);
answerRight.startAnimation(AnimationUtils.loadAnimation(view.getContext(), R.anim.grow_from_middle));
answerWrong.setVisibility(View.VISIBLE);
answerWrong.startAnimation(AnimationUtils.loadAnimation(view.getContext(), R.anim.grow_from_middle));
return false;
}
});
Here is the code for my gesture dector:
gestureDetectorScore = new GestureDetector(new ScoreGestureDetector());
gestureListenerScore = new View.OnTouchListener() {
public boolean onTouch(View v, MotionEvent event) {
if (gestureDetectorScore.onTouchEvent(event)) {
return true;
}
return false;
}
};
private class ScoreGestureDetector extends SimpleOnGestureListener {
private static final int SWIPE_MIN_DISTANCE = 5;
private static final int SWIPE_THRESHOLD_VELOCITY = 2;
#Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
try {
//if (Math.abs(e1.getX() - e2.getX()) > SWIPE_MAX_OFF_PATH)
//return false;
// right to left swipe
//if(e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
if(e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE){
Toast.makeText(AndroidOrientationSensor.this, "Up Swipe", Toast.LENGTH_SHORT).show();
}
//else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE){
Toast.makeText(AndroidOrientationSensor.this, "Down Swipe", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
// nothing
}
return false;
}
#Override
public boolean onDown(MotionEvent e1){
Toast.makeText(AndroidOrientationSensor.this, "Up Swipe", Toast.LENGTH_SHORT).show();
return true;
}
}
Finally, I am setting the gesture dectector to the the "flipCard" view like this:
flipCard.setOnTouchListener(gestureListenerScore);
Any help would be greatly appreciated.
I think the problem you're going to have is if the touch event is consumed by the button, then the second view won't receive it.
Even if it did, the fling I imagine will only get called when the user performs a fling from start to finish.
The only thing I can think of doing (despite it being very horrible / hacky) is to try and inset a fake up touch event. This might then allow the user to perform the fling, but this isn't particularly stable either.
Better way to handle it would be to have a view group consume the touch events, pass the touch events to a gesture detector to detect a long click, then set a flag within the view groups touch listener to indicate a fling is expected, then manually detect when prev y and current y go beyond a threshold. This will give you some idea of velocity but probably no where near as meaningful as the velocity provided in the onFling callback method.
What I ended up doing to solve this problem is placing a gestureDectector on the view and overriding the onDown method. This allowed me to simulate a click event. I was unable to get both a long click and a swipe event, but for my purposes the click event triggers during the swipe which seems to work well enough. Thanks to GauntFace for the inspiration.
The GestureListener also has a onLongPress event.

Categories

Resources