Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RxView scrollChangeEvents for api lower than M #433

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ inline fun View.preDraws(proceedDrawingPass: Callable<Boolean>): Observable<Unit
* *Warning:* The created observable keeps a strong reference to `view`. Unsubscribe
* to free this reference.
*/
@RequiresApi(23)
@RequiresApi(16)
@CheckResult
inline fun View.scrollChangeEvents(): Observable<ViewScrollChangeEvent> = RxView.scrollChangeEvents(this)

Expand Down
1 change: 1 addition & 0 deletions rxbinding/src/androidTest/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

<application>
<activity android:name="com.jakewharton.rxbinding2.view.RxViewAttachTestActivity"/>
<activity android:name="com.jakewharton.rxbinding2.view.RxViewScrollTestActivity"/>
<activity android:name="com.jakewharton.rxbinding2.view.RxViewSystemUiVisibilityTestActivity"/>
<activity android:name="com.jakewharton.rxbinding2.widget.RxAdapterViewTestActivity"/>
<activity android:name="com.jakewharton.rxbinding2.widget.RxAbsListViewTestActivity"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package com.jakewharton.rxbinding2.view;

import android.app.Instrumentation;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SdkSuppress;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.view.View;

import com.jakewharton.rxbinding2.RecordingObserver;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import io.reactivex.android.schedulers.AndroidSchedulers;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;

@RunWith(AndroidJUnit4.class)
public final class RxViewScrollTest {
@Rule public final ActivityTestRule<RxViewScrollTestActivity> activityRule =
new ActivityTestRule<>(RxViewScrollTestActivity.class);

private final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
private View view;

@Before
public void setUp() {
RxViewScrollTestActivity activity = activityRule.getActivity();
view = activity.view;
}

@SdkSuppress(minSdkVersion = 16)
@Test public void scrollChangeEvents() {
RecordingObserver<ViewScrollChangeEvent> o = new RecordingObserver<>();
RxView.scrollChangeEvents(view)
.subscribeOn(AndroidSchedulers.mainThread())
.subscribe(o);
o.assertNoMoreEvents();


instrumentation.runOnMainSync(
new Runnable() {
@Override public void run() {
view.scrollTo(1, 1);
}
});
ViewScrollChangeEvent event0 = o.takeNext();
assertSame(view, event0.view());
assertEquals(1, event0.scrollX());
assertEquals(1, event0.scrollY());
assertEquals(0, event0.oldScrollX());
assertEquals(0, event0.oldScrollY());

instrumentation.runOnMainSync(
new Runnable() {
@Override public void run() {
view.scrollTo(2, 2);
}
});
ViewScrollChangeEvent event1 = o.takeNext();
assertSame(view, event1.view());
assertEquals(2, event1.scrollX());
assertEquals(2, event1.scrollY());
assertEquals(1, event1.oldScrollX());
assertEquals(1, event1.oldScrollY());

o.dispose();
instrumentation.runOnMainSync(
new Runnable() {
@Override public void run() {
view.scrollTo(3, 3);
}
});
o.assertNoMoreEvents();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.jakewharton.rxbinding2.view;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;

public final class RxViewScrollTestActivity extends Activity {
View view;

@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
view = new View(this);

setContentView(view);
}
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.jakewharton.rxbinding2.view;

import android.os.Build;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
Expand Down Expand Up @@ -295,11 +296,15 @@ public static Observable<Object> preDraws(@NonNull View view,
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*/
@RequiresApi(23)
@RequiresApi(16)
@CheckResult @NonNull
public static Observable<ViewScrollChangeEvent> scrollChangeEvents(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewScrollChangeEventObservable(view);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return new ViewScrollChangeEventObservable(view);
} else {
return new ViewScrollChangeEventObservableCompat(view);
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.jakewharton.rxbinding2.view;

import android.support.annotation.RequiresApi;
import android.view.View;
import android.view.ViewTreeObserver;

import io.reactivex.Observable;
import io.reactivex.Observer;
import io.reactivex.android.MainThreadDisposable;

import static com.jakewharton.rxbinding2.internal.Preconditions.checkMainThread;

@RequiresApi(16)
final class ViewScrollChangeEventObservableCompat extends Observable<ViewScrollChangeEvent> {
private final View view;

ViewScrollChangeEventObservableCompat(View view) {
this.view = view;
}

@Override
protected void subscribeActual(Observer<? super ViewScrollChangeEvent> observer) {
if (!checkMainThread(observer)) {
return;
}
Listener listener = new Listener(view, observer);
observer.onSubscribe(listener);
setOnScrollChangeListenerWith(view, listener);
}

private void setOnScrollChangeListenerWith(final View v, final Listener listener) {
ViewTreeObserver viewTreeObserver = v.getViewTreeObserver();
viewTreeObserver.addOnScrollChangedListener(listener);
}

static final class Listener extends MainThreadDisposable implements OnScrollChangeListener,
ViewTreeObserver.OnScrollChangedListener {
private final View view;
private final Observer<? super ViewScrollChangeEvent> observer;
private int oldl, oldt;

Listener(View view, Observer<? super ViewScrollChangeEvent> observer) {
this.view = view;
this.observer = observer;
}

@Override
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
if (!isDisposed()) {
observer.onNext(ViewScrollChangeEvent.create(v, scrollX, scrollY, oldScrollX, oldScrollY));
}
}

@Override protected void onDispose() {
ViewTreeObserver viewTreeObserver = view.getViewTreeObserver();
viewTreeObserver.removeOnScrollChangedListener(this);
}

@Override
public void onScrollChanged() {
this.onScrollChange(view, view.getScrollX(), view.getScrollY(), oldl, oldt);
oldl = view.getScrollX();
oldt = view.getScrollY();
}
}

public interface OnScrollChangeListener {
/**
* Called when the scroll position of a view changes.
*
* @param v The view whose scroll position has changed.
* @param scrollX Current horizontal scroll origin.
* @param scrollY Current vertical scroll origin.
* @param oldScrollX Previous horizontal scroll origin.
* @param oldScrollY Previous vertical scroll origin.
*/
void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX, int oldScrollY);
}
}