Skip to content
This repository was archived by the owner on Oct 24, 2025. It is now read-only.
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);
}
}