From ab174253653a4d432948714e6fbd4ad8571ef092 Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Fri, 4 Sep 2015 10:03:33 -0500 Subject: [PATCH 1/3] first pass at gestures upport. --- .../view/ObservableGestureListener.java | 55 +++++++++ .../rxbinding/view/RxGestures.java | 110 ++++++++++++++++++ .../rxbinding/view/ViewGestureEvent.java | 43 +++++++ .../rxbinding/view/ViewGestureFlingEvent.java | 66 +++++++++++ .../view/ViewGestureScrollEvent.java | 66 +++++++++++ 5 files changed, 340 insertions(+) create mode 100644 rxbinding/src/main/java/com/jakewharton/rxbinding/view/ObservableGestureListener.java create mode 100644 rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxGestures.java create mode 100644 rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewGestureEvent.java create mode 100644 rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewGestureFlingEvent.java create mode 100644 rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewGestureScrollEvent.java diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ObservableGestureListener.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ObservableGestureListener.java new file mode 100644 index 00000000..5027b100 --- /dev/null +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ObservableGestureListener.java @@ -0,0 +1,55 @@ +package com.jakewharton.rxbinding.view; + +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import rx.subjects.PublishSubject; + +final class ObservableGestureListener implements GestureDetector.OnGestureListener { + + final View view; + + PublishSubject downGestureObservable = PublishSubject.create(); + PublishSubject showPressGestureObservable = PublishSubject.create(); + PublishSubject singleTapUpGestureObservable = PublishSubject.create(); + PublishSubject scrollGestureObservable = PublishSubject.create(); + PublishSubject longPressGestureObservable = PublishSubject.create(); + PublishSubject flingGestureObservable = PublishSubject.create(); + + ObservableGestureListener(View view) { + this.view = view; + } + + @Override public boolean onDown(MotionEvent e) { + downGestureObservable.onNext(ViewGestureEvent.create(view, e)); + return false; + } + + @Override public void onShowPress(MotionEvent e) { + showPressGestureObservable.onNext(ViewGestureEvent.create(view, e)); + } + + @Override public boolean onSingleTapUp(MotionEvent e) { + singleTapUpGestureObservable.onNext(ViewGestureEvent.create(view, e)); + return false; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + scrollGestureObservable.onNext( + ViewGestureScrollEvent.create(view, e1, e2, distanceX, distanceY)); + return false; + } + + @Override public void onLongPress(MotionEvent e) { + longPressGestureObservable.onNext(ViewGestureEvent.create(view, e)); + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + flingGestureObservable.onNext( + ViewGestureFlingEvent.create(view, e1, e2, velocityX, velocityY)); + return false; + } +} \ No newline at end of file diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxGestures.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxGestures.java new file mode 100644 index 00000000..e5adf179 --- /dev/null +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxGestures.java @@ -0,0 +1,110 @@ +package com.jakewharton.rxbinding.view; + +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import rx.Observable; +import rx.functions.Action1; +import rx.functions.Func1; + +/** + * Attaches a GestureDetector to the supplied touch event observable to be notified of + * gesture events. + *

+ * {@code + * Observable touches = RxView.touches(view); + * RxGestures gestures = RxGestures.withTouches(view, touches); + * Observable scrolls = gestures.scroll(); + * } + *

+ * Warning: Instances keep a strong reference to the view. Operators that + * cache instances have the potential to leak the associated {@code Context}. + */ +public class RxGestures { + + private final Observable motionEventObservable; + private final GestureDetector gestureDetector; + private final ObservableGestureListener gestureListener; + + /** + * Create an {@code RxGestures} object that listens to motion events. + * @param motionEvents + * @return + */ + public static RxGestures withMotionEvents(View view, Observable motionEvents) { + return new RxGestures(view, motionEvents); + } + + /** + * Create an {@code RxGestures} object that listens to touch events. + * + * @param viewTouchEvents An observable of {@link ViewTouchEvent}s returned from + * {@code RxView#touchEvents(View view)}. + * @return A new RxGestures object listening for gestures. + */ + public static RxGestures withViewTouchEvents(View view, + Observable viewTouchEvents) { + return new RxGestures(view, viewTouchEvents + .map(new Func1() { + @Override public MotionEvent call(ViewTouchEvent viewTouchEvent) { + return viewTouchEvent.motionEvent(); + } + })); + } + + private RxGestures(View view, Observable motionEventObservable) { + this.motionEventObservable = motionEventObservable; + this.gestureListener = new ObservableGestureListener(view); + this.gestureDetector = new GestureDetector(view.getContext(), gestureListener); + + motionEventObservable.subscribe(new Action1() { + @Override public void call(MotionEvent motionEvent) { + gestureDetector.onTouchEvent(motionEvent); + } + }); + } + + /** + * Create an observable of down gestures. + */ + public Observable down() { + return gestureListener.downGestureObservable; + } + + /** + * Create an observable of show press gestures. + */ + public Observable showPress() { + return gestureListener.showPressGestureObservable; + } + + /** + * Create an observable of single tap up gestures. + */ + public Observable singleTapUp() { + return gestureListener.singleTapUpGestureObservable; + } + + /** + * Create an observable of scroll gestures. + */ + public Observable scroll() { + return gestureListener.scrollGestureObservable; + } + + /** + * Create an observable of long press gestures. + */ + public Observable longPress() { + return gestureListener.longPressGestureObservable; + } + + /** + * Create an observable of fling gestures. + */ + public Observable fling() { + return gestureListener.flingGestureObservable; + } + +} diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewGestureEvent.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewGestureEvent.java new file mode 100644 index 00000000..dee8b932 --- /dev/null +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewGestureEvent.java @@ -0,0 +1,43 @@ +package com.jakewharton.rxbinding.view; + +import android.support.annotation.CheckResult; +import android.support.annotation.NonNull; +import android.view.MotionEvent; +import android.view.View; + +public final class ViewGestureEvent extends ViewEvent { + @CheckResult @NonNull + public static ViewGestureEvent create(@NonNull View view, @NonNull MotionEvent motionEvent) { + return new ViewGestureEvent(view, motionEvent); + } + + private final MotionEvent motionEvent; + + private ViewGestureEvent(@NonNull View view, @NonNull MotionEvent motionEvent) { + super(view); + this.motionEvent = motionEvent; + } + + @NonNull + public MotionEvent motionEvent() { + return motionEvent; + } + + @Override public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof ViewGestureEvent)) return false; + ViewGestureEvent other = (ViewGestureEvent) o; + return other.view() == view() && other.motionEvent.equals(motionEvent); + } + + @Override public int hashCode() { + int result = 17; + result = result * 37 + view().hashCode(); + result = result * 37 + motionEvent.hashCode(); + return result; + } + + @Override public String toString() { + return "ViewGestureEvent{view=" + view() + ", motionEvent=" + motionEvent + '}'; + } +} diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewGestureFlingEvent.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewGestureFlingEvent.java new file mode 100644 index 00000000..a442575e --- /dev/null +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewGestureFlingEvent.java @@ -0,0 +1,66 @@ +package com.jakewharton.rxbinding.view; + +import android.support.annotation.CheckResult; +import android.support.annotation.NonNull; +import android.view.MotionEvent; +import android.view.View; + +public final class ViewGestureFlingEvent extends ViewEvent { + @CheckResult @NonNull + public static ViewGestureFlingEvent create(@NonNull View view, @NonNull MotionEvent e1, + @NonNull MotionEvent e2, float velocityX, float velocityY) { + return new ViewGestureFlingEvent(view, e1, e2, velocityX, velocityY); + } + + private final MotionEvent e1, e2; + private final float velocityX, velocityY; + + private ViewGestureFlingEvent(@NonNull View view, @NonNull MotionEvent e1, + @NonNull MotionEvent e2, float velocityX, float velocityY) { + super(view); + this.e1 = e1; + this.e2 = e2; + this.velocityX = velocityX; + this.velocityY = velocityY; + } + + @NonNull public MotionEvent e1() { + return e1; + } + + @NonNull public MotionEvent e2() { + return e2; + } + + public float velocityX() { + return velocityX; + } + + public float velocityY() { + return velocityY; + } + + @Override public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ViewGestureFlingEvent)) return false; + ViewGestureFlingEvent other = (ViewGestureFlingEvent) o; + return other.view() == view() && other.e1.equals(e1) && other.e2.equals(e2) + && Float.compare(other.velocityX, velocityX) == 0 + && Float.compare(other.velocityY, velocityY) == 0; + } + + @Override public int hashCode() { + int result = 17; + result = result * 37 + view().hashCode(); + result = result * 37 + e1.hashCode(); + result = result * 37 + e2.hashCode(); + result = result * 37 + (velocityX != +0.0f ? Float.floatToIntBits(velocityX) : 0); + result = result * 37 + (velocityY != +0.0f ? Float.floatToIntBits(velocityY) : 0); + return result; + } + + @Override public String toString() { + return "ViewScrollGestureEvent{view=" + view() + ", e1=" + e1 + ", e2=" + e2 + + ", velocityX=" + velocityX + ", velocityY=" + velocityY + '}'; + } +} diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewGestureScrollEvent.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewGestureScrollEvent.java new file mode 100644 index 00000000..4bbbee18 --- /dev/null +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ViewGestureScrollEvent.java @@ -0,0 +1,66 @@ +package com.jakewharton.rxbinding.view; + +import android.support.annotation.CheckResult; +import android.support.annotation.NonNull; +import android.view.MotionEvent; +import android.view.View; + +public final class ViewGestureScrollEvent extends ViewEvent { + @CheckResult @NonNull + public static ViewGestureScrollEvent create(@NonNull View view, @NonNull MotionEvent e1, + @NonNull MotionEvent e2, float distanceX, float distanceY) { + return new ViewGestureScrollEvent(view, e1, e2, distanceX, distanceY); + } + + private final MotionEvent e1, e2; + private final float distanceX, distanceY; + + private ViewGestureScrollEvent(@NonNull View view, @NonNull MotionEvent e1, + @NonNull MotionEvent e2, float distanceX, float distanceY) { + super(view); + this.e1 = e1; + this.e2 = e2; + this.distanceX = distanceX; + this.distanceY = distanceY; + } + + @NonNull public MotionEvent e1() { + return e1; + } + + @NonNull public MotionEvent e2() { + return e2; + } + + public float distanceX() { + return distanceX; + } + + public float distanceY() { + return distanceY; + } + + @Override public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ViewGestureScrollEvent)) return false; + ViewGestureScrollEvent other = (ViewGestureScrollEvent) o; + return other.view() == view() && other.e1.equals(e1) && other.e2.equals(e2) + && Float.compare(other.distanceX, distanceX) == 0 + && Float.compare(other.distanceY, distanceY) == 0; + } + + @Override public int hashCode() { + int result = 17; + result = result * 37 + view().hashCode(); + result = result * 37 + e1.hashCode(); + result = result * 37 + e2.hashCode(); + result = result * 37 + (distanceX != +0.0f ? Float.floatToIntBits(distanceX) : 0); + result = result * 37 + (distanceY != +0.0f ? Float.floatToIntBits(distanceY) : 0); + return result; + } + + @Override public String toString() { + return "ViewScrollGestureEvent{view=" + view() + ", e1=" + e1 + ", e2=" + e2 + + ", distanceX=" + distanceX + ", distanceY=" + distanceY + '}'; + } +} From 40840ad3c55c2af9127ee62210cfe8290baf0422 Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Fri, 4 Sep 2015 10:23:26 -0500 Subject: [PATCH 2/3] Adds lazy creation of gesture subjects. --- .../view/ObservableGestureListener.java | 83 +++++++++++++++---- .../rxbinding/view/RxGestures.java | 12 +-- 2 files changed, 75 insertions(+), 20 deletions(-) diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ObservableGestureListener.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ObservableGestureListener.java index 5027b100..9927903d 100644 --- a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ObservableGestureListener.java +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/ObservableGestureListener.java @@ -4,52 +4,107 @@ import android.view.MotionEvent; import android.view.View; +import rx.Observable; import rx.subjects.PublishSubject; final class ObservableGestureListener implements GestureDetector.OnGestureListener { final View view; - PublishSubject downGestureObservable = PublishSubject.create(); - PublishSubject showPressGestureObservable = PublishSubject.create(); - PublishSubject singleTapUpGestureObservable = PublishSubject.create(); - PublishSubject scrollGestureObservable = PublishSubject.create(); - PublishSubject longPressGestureObservable = PublishSubject.create(); - PublishSubject flingGestureObservable = PublishSubject.create(); + private PublishSubject downGestureObservable; + private PublishSubject showPressGestureObservable; + private PublishSubject singleTapUpGestureObservable; + private PublishSubject scrollGestureObservable; + private PublishSubject longPressGestureObservable; + private PublishSubject flingGestureObservable; ObservableGestureListener(View view) { this.view = view; } + public Observable downObservable() { + if (downGestureObservable == null) { + downGestureObservable = PublishSubject.create(); + } + return downGestureObservable; + } + + public Observable showPressObservable() { + if (showPressGestureObservable == null) { + showPressGestureObservable = PublishSubject.create(); + } + return showPressGestureObservable; + } + + public Observable singleTapUpObservable() { + if (singleTapUpGestureObservable == null) { + singleTapUpGestureObservable = PublishSubject.create(); + } + return singleTapUpGestureObservable; + } + + public Observable scrollObservable() { + if (scrollGestureObservable == null) { + scrollGestureObservable = PublishSubject.create(); + } + return scrollGestureObservable; + } + + public Observable longPressObservable() { + if (longPressGestureObservable == null) { + longPressGestureObservable = PublishSubject.create(); + } + return longPressGestureObservable; + } + + public Observable flingObservable() { + if (flingGestureObservable == null) { + flingGestureObservable = PublishSubject.create(); + } + return flingGestureObservable; + } + @Override public boolean onDown(MotionEvent e) { - downGestureObservable.onNext(ViewGestureEvent.create(view, e)); + if (downGestureObservable != null) { + downGestureObservable.onNext(ViewGestureEvent.create(view, e)); + } return false; } @Override public void onShowPress(MotionEvent e) { - showPressGestureObservable.onNext(ViewGestureEvent.create(view, e)); + if (showPressGestureObservable != null) { + showPressGestureObservable.onNext(ViewGestureEvent.create(view, e)); + } } @Override public boolean onSingleTapUp(MotionEvent e) { - singleTapUpGestureObservable.onNext(ViewGestureEvent.create(view, e)); + if (singleTapUpGestureObservable != null) { + singleTapUpGestureObservable.onNext(ViewGestureEvent.create(view, e)); + } return false; } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - scrollGestureObservable.onNext( - ViewGestureScrollEvent.create(view, e1, e2, distanceX, distanceY)); + if (scrollGestureObservable != null) { + scrollGestureObservable.onNext( + ViewGestureScrollEvent.create(view, e1, e2, distanceX, distanceY)); + } return false; } @Override public void onLongPress(MotionEvent e) { - longPressGestureObservable.onNext(ViewGestureEvent.create(view, e)); + if (longPressGestureObservable != null) { + longPressGestureObservable.onNext(ViewGestureEvent.create(view, e)); + } } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - flingGestureObservable.onNext( - ViewGestureFlingEvent.create(view, e1, e2, velocityX, velocityY)); + if (flingGestureObservable != null) { + flingGestureObservable.onNext( + ViewGestureFlingEvent.create(view, e1, e2, velocityX, velocityY)); + } return false; } } \ No newline at end of file diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxGestures.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxGestures.java index e5adf179..04fbf59f 100644 --- a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxGestures.java +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxGestures.java @@ -69,42 +69,42 @@ private RxGestures(View view, Observable motionEventObservable) { * Create an observable of down gestures. */ public Observable down() { - return gestureListener.downGestureObservable; + return gestureListener.downObservable(); } /** * Create an observable of show press gestures. */ public Observable showPress() { - return gestureListener.showPressGestureObservable; + return gestureListener.showPressObservable(); } /** * Create an observable of single tap up gestures. */ public Observable singleTapUp() { - return gestureListener.singleTapUpGestureObservable; + return gestureListener.singleTapUpObservable(); } /** * Create an observable of scroll gestures. */ public Observable scroll() { - return gestureListener.scrollGestureObservable; + return gestureListener.scrollObservable(); } /** * Create an observable of long press gestures. */ public Observable longPress() { - return gestureListener.longPressGestureObservable; + return gestureListener.longPressObservable(); } /** * Create an observable of fling gestures. */ public Observable fling() { - return gestureListener.flingGestureObservable; + return gestureListener.flingObservable(); } } From 9d998fa583a2e47f931389e8eb18bf52d5585859 Mon Sep 17 00:00:00 2001 From: Ryan Harter Date: Wed, 9 Sep 2015 08:24:45 -0500 Subject: [PATCH 3/3] Adds some documentation around connectable observable. --- .../java/com/jakewharton/rxbinding/view/RxGestures.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxGestures.java b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxGestures.java index 04fbf59f..69b3731f 100644 --- a/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxGestures.java +++ b/rxbinding/src/main/java/com/jakewharton/rxbinding/view/RxGestures.java @@ -10,10 +10,13 @@ /** * Attaches a GestureDetector to the supplied touch event observable to be notified of - * gesture events. + * gesture events. The RxGestures instance will subscribe to the supplied Observable, so + * if you'd like to have more than one subscriber you need to wrap it in a + * {@code ConnectableObservable} before passing it into this class. *

* {@code - * Observable touches = RxView.touches(view); + * ConnectableObservable touches = RxView.touches(view).publish(); + * touches.connect(); * RxGestures gestures = RxGestures.withTouches(view, touches); * Observable scrolls = gestures.scroll(); * }