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?
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).
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.
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