Skip to content

Instantly share code, notes, and snippets.

@androidseb
Created March 31, 2014 20:58
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save androidseb/9902093 to your computer and use it in GitHub Desktop.
Save androidseb/9902093 to your computer and use it in GitHub Desktop.
import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.Scroller;
/**
* @author androidseb
*
* Extends Framelayout and uses its first children as a scrollable view.
*/
public class BiDirectionScrollView extends FrameLayout {
public class AndroidUtils{
public static float dipToPixels(final Context _context, final float _dipValue) {
final DisplayMetrics metrics = _context.getResources().getDisplayMetrics();
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, _dipValue, metrics);
}
}
/**
* Factor after which a scrolling direction cancels the other. For example
* if I scroll 10 up and X left, this variable will be used so that X is
* changed to 0 if X * FACTOR < 10. <br>
* For flinging an overriden direction will be canceled until the end of the
* flinging animation<br>
* For regular scrolling, at the time the scroll threshold gets passed, if a
* direction overrides another, it will stay so until the pointer is
* released.
*/
private static final int SCROLL_DIRECTION_OVERRIDE_FACTOR = 3;
private static final float SCROLL_THRESHOLD_DISTANCE_DIP = 48;
private class BiDirectionScrollViewFlinger implements Runnable {
private final BiDirectionScrollView view;
private final Scroller scroller;
private int lastX = 0;
private int lastY = 0;
BiDirectionScrollViewFlinger(final BiDirectionScrollView _view) {
view = _view;
scroller = new Scroller(_view.getContext());
}
void start(final int _velocityX, final int _velocityY) {
final View firstChild = view.getChildAt(0);
if (firstChild == null) {
return;
}
final int velocityX;
final int velocityY;
if (view.currentScrollGestureYOverride) {
velocityX = 0;
} else {
velocityX = _velocityX;
}
if (view.currentScrollGestureXOverride) {
velocityY = 0;
} else {
velocityY = _velocityY;
}
int initialX = view.getScrollX();
int initialY = view.getScrollY();
scroller.fling(0, 0, velocityX, velocityY, -Integer.MAX_VALUE, Integer.MAX_VALUE, -Integer.MAX_VALUE,
Integer.MAX_VALUE);
lastX = initialX;
lastY = initialY;
view.post(this);
}
@Override
public void run() {
if (scroller.isFinished()) {
return;
}
boolean more = scroller.computeScrollOffset();
int x = scroller.getCurrX();
int y = scroller.getCurrY();
view.scrollTo(lastX - x, lastY - y);
if (more) {
view.post(this);
}
}
boolean isFlinging() {
return !scroller.isFinished();
}
void forceFinished() {
if (!scroller.isFinished()) {
scroller.forceFinished(true);
}
}
}
private final BiDirectionScrollViewFlinger flinger;
private final GestureDetector scrollGestureDetector;
private final float scrollThresholdDistance;
private OnTouchListener touchListener = null;
public BiDirectionScrollView(final Context context) {
super(context);
flinger = new BiDirectionScrollViewFlinger(BiDirectionScrollView.this);
scrollGestureDetector = createScrollGestureDetector();
scrollThresholdDistance = AndroidUtils.dipToPixels(getContext(), SCROLL_THRESHOLD_DISTANCE_DIP);
}
public BiDirectionScrollView(final Context context, final AttributeSet attrs) {
super(context, attrs);
flinger = new BiDirectionScrollViewFlinger(BiDirectionScrollView.this);
scrollGestureDetector = createScrollGestureDetector();
scrollThresholdDistance = AndroidUtils.dipToPixels(getContext(), SCROLL_THRESHOLD_DISTANCE_DIP);
}
public BiDirectionScrollView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
flinger = new BiDirectionScrollViewFlinger(BiDirectionScrollView.this);
scrollGestureDetector = createScrollGestureDetector();
scrollThresholdDistance = AndroidUtils.dipToPixels(getContext(), SCROLL_THRESHOLD_DISTANCE_DIP);
}
private GestureDetector createScrollGestureDetector() {
final GestureDetector res = new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX,
final float distanceY) {
if (flinger.isFlinging()) {
flinger.forceFinished();
}
if (currentScrollGestureBroken || !currentlyIntoScrollGesture) {
return false;
}
BiDirectionScrollView.this.scrollBy(currentScrollGestureYOverride ? 0 : (int) distanceX,
currentScrollGestureXOverride ? 0 : (int) distanceY);
return true;
}
@Override
public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX,
final float velocityY) {
if (flinger.isFlinging()) {
flinger.forceFinished();
}
flinger.start((int) velocityX, (int) velocityY);
return super.onFling(e1, e2, velocityX, velocityY);
}
});
res.setIsLongpressEnabled(false);
return res;
}
@Override
public void scrollTo(final int x, final int y) {
super.scrollTo(x, y);
fixScrollIfOutOfBounds();
}
@Override
public void scrollBy(final int x, final int y) {
super.scrollBy(x, y);
fixScrollIfOutOfBounds();
}
private void fixScrollIfOutOfBounds() {
if (getScrollX() < 0) {
scrollTo(0, getScrollY());
}
if (getScrollY() < 0) {
scrollTo(getScrollX(), 0);
}
final View firstChild = getChildAt(0);
if (firstChild == null) {
return;
}
int childWidth = firstChild.getWidth();
int childHeight = firstChild.getHeight();
if (childWidth > getWidth() && getScrollX() + getWidth() > childWidth) {
scrollTo(childWidth - getWidth(), getScrollY());
}
if (childHeight > getHeight() && getScrollY() + getHeight() > childHeight) {
scrollTo(getScrollX(), childHeight - getHeight());
}
}
private boolean currentScrollGestureBroken = false;
private boolean currentlyIntoScrollGesture = false;
private boolean currentScrollGestureXOverride = false;
private boolean currentScrollGestureYOverride = false;
private int initialDownX = -1;
private int initialDownY = -1;
@Override
public void setOnTouchListener(final OnTouchListener l) {
touchListener = l;
}
@Override
public boolean dispatchTouchEvent(final MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
currentScrollGestureBroken = false;
currentlyIntoScrollGesture = false;
initialDownX = (int) ev.getX();
initialDownY = (int) ev.getY();
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
initialDownX = -1;
initialDownY = -1;
} else if (ev.getAction() == MotionEvent.ACTION_MOVE) {
final float scrolledXDistance = Math.abs(ev.getX() - initialDownX);
final float scrolledYDistance = Math.abs(ev.getY() - initialDownY);
if (!currentlyIntoScrollGesture
&& (scrolledXDistance > scrollThresholdDistance || scrolledYDistance > scrollThresholdDistance)) {
currentScrollGestureXOverride = scrolledYDistance * SCROLL_DIRECTION_OVERRIDE_FACTOR < scrolledXDistance;
currentScrollGestureYOverride = scrolledXDistance * SCROLL_DIRECTION_OVERRIDE_FACTOR < scrolledYDistance;
currentlyIntoScrollGesture = true;
}
}
super.dispatchTouchEvent(ev);
if (touchListener != null) {
touchListener.onTouch(this, ev);
}
scrollGestureDetector.onTouchEvent(ev);
return true;
}
/**
* Breaks the current scroll gesture until next time a pointer is pressed.
* Allows to prevent scrolling while dragging in the view if other children
* views want to handle that event without scrolling being triggered.
*/
public void breakCurrentScrollGestureUntilNextPress() {
currentScrollGestureBroken = true;
}
/**
* @return true if the view is currently scrolling, false otherwise. This
* allows to filter long click events being triggered if scrolling
* started by grabbing a point over a child view.
*/
public boolean isCurrentlyIntoScrollGesture() {
return !currentScrollGestureBroken && currentlyIntoScrollGesture;
}
}
@abbasalim
Copy link

please add two finger zoom

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment