I have an implementation BottomDrawerthat implements GestureDetectorto determine when the user throws the bottom box. I have two problems:
1) is onFlingnot always called. This is a relatively minor issue.
2) The speed transmitted to onFling, often has the wrong direction. This is a huge problem, as it causes the user to drop the box to close it and it will open, or vice versa. Is this a problem someone has encountered before? How can this be solved? I thought it might be possible to ignore emissions less than a certain value, but erroneous speeds can sometimes be quite large, so this does not help.
Here is the code of my class BottomDrawer, I can also upload a demo project to GitHub if it were useful:
package com.cbendeb.bottomdrawer.view;
import android.animation.Animator.AnimatorListener;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.widget.LinearLayout;
public final class BottomDrawer extends LinearLayout implements GestureDetector.OnGestureListener {
private static final String TAG = BottomDrawer.class.getSimpleName();
private static final int ANIMATE_DURATION = 500;
private float lastTouchY;
private float openY;
private float closedY;
private float currentY = -1;
private AnimatorListener closeListener;
private AnimatorListener openListener;
private GestureDetector gestureDetector;
private boolean flung = false;
public BottomDrawer(Context context) {
super(context);
init(context);
}
public BottomDrawer(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public BottomDrawer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context);
}
private void init(Context context) {
setOrientation(LinearLayout.VERTICAL);
gestureDetector = new GestureDetector(context, this);
}
public boolean isOpen() {
return currentY != closedY;
}
public void open() {
animate()
.translationY(0)
.setDuration(
Math.max(0, (int) (ANIMATE_DURATION * (getY() - openY) / (closedY - openY))))
.setListener(openListener).start();
}
public void close() {
animate()
.translationY(getChildAt(1).getHeight())
.setDuration(
Math.max(0,
(int) (ANIMATE_DURATION * (closedY - getY()) / (closedY - openY))))
.setListener(closeListener).start();
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
final int action = ev.getActionMasked();
final float y = ev.getRawY();
gestureDetector.onTouchEvent(ev);
switch (action) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
final float dy = y - lastTouchY;
if (getY() + dy < closedY && getY() + dy > openY) {
setY(getY() + dy);
invalidate();
}
break;
case MotionEvent.ACTION_CANCEL:
flung = false;
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "flung: " + flung);
if (!flung) {
if (getY() >= (openY + closedY) / 2) {
Log.d(TAG, "closing");
close();
} else {
Log.d(TAG, "opening");
open();
}
}
flung = false;
break;
case MotionEvent.ACTION_POINTER_UP:
break;
}
lastTouchY = y;
return true;
}
@Override
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
openY = getBottom() - getChildAt(0).getHeight() - getChildAt(1).getHeight();
closedY = getBottom() - getChildAt(0).getHeight();
if (currentY == -1) {
currentY = closedY;
}
if (changed && isOpen()) {
setY(openY);
} else if (changed && !isOpen()) {
setY(closedY);
}
}
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
Log.d(TAG, "onFling velocityY: " + velocityY + " currentY " + currentY);
final float y = getY();
if (velocityY < 0 && y > openY && y < closedY) {
Log.d(TAG, "onFling opening");
open();
} else if (velocityY > 0 && y > openY && y < closedY) {
Log.d(TAG, "onFling closing");
close();
}
flung = true;
return true;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
}
I am looking at the implementation of the GestureDetector on GrepCode ... but it would be nice if there was an easier way to resolve this.