Different fling (swipe) velocity on different Android devices with same density

14,815

Solution 1

I had a similar problem. Instead of working directly with the max and min fling velocities from ViewConfiguration, you can normalize the velocity to a value between 0 and 1.

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    float maxFlingVelocity    = ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity();
    float velocityPercentX    = velocityX / maxFlingVelocity;          // the percent is a value in the range of (0, 1]
    float normalizedVelocityX = velocityPercentX * PIXELS_PER_SECOND;  // where PIXELS_PER_SECOND is a device-independent measurement

In other words, velocityPercentX gives you the "power" of the fling as a percent, and normalizedVelocityX is the velocity in terms of your application logic (such as an image width in device-independent pixels).

Solution 2

How come different devices with same density and almost same api level don't have the same max velocity?

Min and max velocity parameters are set by the manufacturer. Different device models will often use the same density bucket for resource selection purposes, but don't normally share the same physical density. Assume the existence of a 4" phone and a 5" phone with identical resolutions and an identical reported density of 1.5. Imagine swiping over both screens at the same physical speed. The reported pixels/second would be higher on the small screen despite the fact that both phones have identical resolutions and reported densities.

What to do with min and max velocity? Lets say the maker of the smaller screen determines 75 pixels/second is the perfect minimum finger velocity before reporting a fling gesture. Any smaller would result in accidental flings from touch error, any larger would require too deliberate of a gesture to fling something. If the manufacturer of the large phone wanted to match the same perfect physical behavior (same finger speed) of the smaller phone, they would need to adjust the minimum velocity to be a smaller number. The maker of the larger device would require less pixels/second to initiate a fling due to physical characteristics. Other factors like screen sensitivity probably factor in for different manufacturers.

How can I get the same experience on different devices when a user makes a swipe gesture?

Use the velocity given in pixels/second to configure the animations. Simply avoid the use of density independent parameters and you should be able to match the velocity exactly. Notice that this has nothing to do with the min or max velocity values. However, you would want to calculate your SWIPE_THRESHOLD_VELOCITY to be a 'raw' pixel value based on the stated density. This is how Android's Launcher2 calculates density independent threshold velocity for home screen item flings:

int DENSITY_INDEPENDENT_THRESHOLD = 200;
Resources r = getResources();
float density = r.getDisplayMetrics().density;
SWIPE_THRESHOLD_VELOCITY = (int)(DENSITY_INDEPENDENT_THRESHOLD * density);

Notice this threshold would be the same for both the 4" phone and the 5" phone in the example above. This does not affect actual velocities because we're only talking about a threshold here. You would simply need to swipe 20% faster on the 5" phone than on the 4" phone to break the threshold. The Android launcher uses a DENSITY_INDEPENDENT_THRESHOLD of -1500 in the y direction, but 200 sounds about right for your application.

Can I use the max velocity data to make a uniform experience across all Android devices?

No. Max velocity is an edge case you don't need to animate correctly. I doubt users will fling anything at the max velocity unless they're playing a game, and users won't care if the animation matches perfectly at a velocity of 6000 pixels/second anyway.

TL;DR Use raw velocities from onFling arguments to configure animations and match speeds exactly. Avoid using density independent parameters to configure fling animations.

Share:
14,815

Related videos on Youtube

Ron Tesler
Author by

Ron Tesler

Updated on October 13, 2020

Comments

  • Ron Tesler
    Ron Tesler over 3 years

    I'm writing my own image viewer that enables users to swipe left\right to see the next\previous image. I want to animate the image change according to the fling velocity.

    To detect a fling gesture and its velocity, I followed this basic gesture detection and did as the accepted answer suggested:

    public class SelectFilterActivity extends Activity implements OnClickListener
    {
    
        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;
        View.OnTouchListener gestureListener;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            /* ... */
    
            // Gesture detection
            gestureDetector = new GestureDetector(this, new MyGestureDetector());
            gestureListener = new View.OnTouchListener() {
                public boolean onTouch(View v, MotionEvent event) {
                    return gestureDetector.onTouchEvent(event);
                }
            };
    
        }
    
        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) {
                        Toast.makeText(SelectFilterActivity.this, "Left Swipe", Toast.LENGTH_SHORT).show();
                    }  else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                        Toast.makeText(SelectFilterActivity.this, "Right Swipe", Toast.LENGTH_SHORT).show();
                    }
                } catch (Exception e) {
                    // nothing
                }
                return false;
            }
    
        }
    

    The problem is that I get different velocity values (velocityX & velocityY) for different Android devices with same density. Basically it means that in one device the swipe feels slow and not-sensitive, and other devices feels too sensitive.

    I thought it might have something to do with the "default" maximum velocity on the device, that can be fetched using -

    ViewConfiguration.get(mContext).getScaledMaximumFlingVelocity()
    

    The results on different devices with same density aren't the same as expected:

        Samsung S I 480x800 density 1.5 Android 2.1
        getScaledMinimumFlingVelocity(): 75
        getScaledMaximumFlingVelocity(): 3600
        ViewConfiguration.MAXIMUM_FLING_VELOCITY = 4000
    
        HTC Desire 480x800 density 1.5 Android 2.3.3
        getScaledMinimumFlingVelocity(): 75
        getScaledMaximumFlingVelocity(): 6000
        ViewConfiguration.MAXIMUM_FLING_VELOCITY = 4000
    
        Samsung S III mini 480x800 density 1.5 Android 4.1
        getScaledMinimumFlingVelocity(): 75
        getScaledMaximumFlingVelocity(): 12000
        ViewConfiguration.MAXIMUM_FLING_VELOCITY = 8000
    

    You can also see that ViewConfiguration has 2 different values for MAXIMUM_FLING_VELOCITY in Android below 4.0 and above.

    How come different devices with same density and almost same api level don't have the same max velocity? How can I get the same experience on different devices when a user makes a swipe gesture? Can I use the max velocity data to make a uniform experience across all Android devices?

  • Ron Tesler
    Ron Tesler over 10 years
    Thanks. My problem isn't with detecting the direction or ignoring non-fling gestures, my problem is is with the velocity differences on same density devices after a fling gesture occurs.
  • superuser
    superuser over 10 years
    @RonTesler You can just choose the velocity based on the density programmatically. You get the density in the onCreate and alter the maxVelocity variable based on the density.
  • Ron Tesler
    Ron Tesler over 10 years
    Sorry, I'm not sure I understand how it answers my question.
  • superuser
    superuser over 10 years
    Instead of using the ViewConfiguration.MAXIMUM_FLING_VELOCITY = 8000,
  • superuser
    superuser over 10 years
    @RonTesler you can just set the MAX_FLING_VELOCITY in your oncreate method of your activity based on the density of a device: EI you can say if the device's density is x, then the MAX_VElOCITY is y
  • Ron Tesler
    Ron Tesler over 10 years
    The max velocity doesn't affect the velocities that are between the min and max velocities. I get different experience on these velocities. In some device I reach the max velocity by a small finger movement and in other device with same density, it doesn't really move much and doesn't reach the max velocity.
  • superuser
    superuser over 10 years
    @RonTesler sorry, I meant min_velocity. :)
  • tim4dev
    tim4dev almost 6 years
    On the contrary. The calculation of coordinates is a dead-end path. Coordinates, sizes, distances depend on many factors. We are given speed - an important general indicator, which should be used in this case.
  • rstojano
    rstojano about 5 years
    Where do you get PIXELS_PER_SECOND from?
  • Bryan W. Wagner
    Bryan W. Wagner about 5 years
    @rstojano it's arbitrary, so you define it as a float constant of whatever you want. If you're scrolling a screen, start with public static final float PIXELS_PER_SECOND = 10; and see how you like it. The above normalizes flings across different devices.
  • rstojano
    rstojano about 5 years
    Thanks for clearing that up @bryan, makes sense to me now.