package com.jakewharton.rxbinding2.view;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import com.jakewharton.rxbinding2.InitialValueObservable;
import io.reactivex.Observable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Predicate;
import java.util.concurrent.Callable;
import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.M;
import static com.jakewharton.rxbinding2.internal.Preconditions.checkNotNull;
import static com.jakewharton.rxbinding2.internal.Functions.CALLABLE_ALWAYS_TRUE;
import static com.jakewharton.rxbinding2.internal.Functions.PREDICATE_ALWAYS_TRUE;
/**
* Static factory methods for creating {@linkplain Observable observables} and {@linkplain Consumer
* actions} for {@link View}.
*/
public final class RxView {
/**
* Create an observable which emits on {@code view} attach events. The emitted value is
* unspecified and should only be used as notification.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*/
@CheckResult @NonNull
public static Observable<Object> attaches(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewAttachesObservable(view, true);
}
/**
* Create an observable of attach and detach events on {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*/
@CheckResult @NonNull
public static Observable<ViewAttachEvent> attachEvents(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewAttachEventObservable(view);
}
/**
* Create an observable which emits on {@code view} detach events. The emitted value is
* unspecified and should only be used as notification.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*/
@CheckResult @NonNull
public static Observable<Object> detaches(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewAttachesObservable(view, false);
}
/**
* Create an observable which emits on {@code view} click events. The emitted value is
* unspecified and should only be used as notification.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses {@link View#setOnClickListener} to observe
* clicks. Only one observable can be used for a view at a time.
*/
@CheckResult @NonNull
public static Observable<Object> clicks(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewClickObservable(view);
}
/**
* Create an observable of {@link DragEvent} for drags on {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses {@link View#setOnDragListener} to observe
* drags. Only one observable can be used for a view at a time.
*/
@CheckResult @NonNull
public static Observable<DragEvent> drags(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewDragObservable(view, PREDICATE_ALWAYS_TRUE);
}
/**
* Create an observable of {@link DragEvent} for {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses {@link View#setOnDragListener} to observe
* drags. Only one observable can be used for a view at a time.
*
* @param handled Predicate invoked with each value to determine the return value of the
* underlying {@link View.OnDragListener}.
*/
@CheckResult @NonNull
public static Observable<DragEvent> drags(@NonNull View view,
@NonNull Predicate<? super DragEvent> handled) {
checkNotNull(view, "view == null");
checkNotNull(handled, "handled == null");
return new ViewDragObservable(view, handled);
}
/**
* Create an observable for draws on {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses {@link ViewTreeObserver#addOnDrawListener} to
* observe draws. Multiple observables can be used for a view at a time.
*/
@RequiresApi(JELLY_BEAN)
@CheckResult @NonNull
public static Observable<Object> draws(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewTreeObserverDrawObservable(view);
}
/**
* Create an observable of booleans representing the focus of {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses {@link View#setOnFocusChangeListener} to observe
* focus change. Only one observable can be used for a view at a time.
* <p>
* <em>Note:</em> A value will be emitted immediately on subscribe.
*/
@CheckResult @NonNull
public static InitialValueObservable<Boolean> focusChanges(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewFocusChangeObservable(view);
}
/**
* Create an observable which emits on {@code view} globalLayout events. The emitted value is
* unspecified and should only be used as notification.
* <p></p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses {@link
* ViewTreeObserver#addOnGlobalLayoutListener} to observe global layouts. Multiple observables
* can be used for a view at a time.
*/
@CheckResult @NonNull
public static Observable<Object> globalLayouts(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewTreeObserverGlobalLayoutObservable(view);
}
/**
* Create an observable of hover events for {@code view}.
* <p>
* <em>Warning:</em> Values emitted by this observable are <b>mutable</b> and part of a shared
* object pool and thus are <b>not safe</b> to cache or delay reading (such as by observing
* on a different thread). If you want to cache or delay reading the items emitted then you must
* map values through a function which calls {@link MotionEvent#obtain(MotionEvent)} or
* {@link MotionEvent#obtainNoHistory(MotionEvent)} to create a copy.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses {@link View#setOnHoverListener} to observe
* touches. Only one observable can be used for a view at a time.
*/
@CheckResult @NonNull
public static Observable<MotionEvent> hovers(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewHoverObservable(view, PREDICATE_ALWAYS_TRUE);
}
/**
* Create an observable of hover events for {@code view}.
* <p>
* <em>Warning:</em> Values emitted by this observable are <b>mutable</b> and part of a shared
* object pool and thus are <b>not safe</b> to cache or delay reading (such as by observing
* on a different thread). If you want to cache or delay reading the items emitted then you must
* map values through a function which calls {@link MotionEvent#obtain(MotionEvent)} or
* {@link MotionEvent#obtainNoHistory(MotionEvent)} to create a copy.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses {@link View#setOnHoverListener} to observe
* touches. Only one observable can be used for a view at a time.
*
* @param handled Predicate invoked with each value to determine the return value of the
* underlying {@link View.OnHoverListener}.
*/
@CheckResult @NonNull
public static Observable<MotionEvent> hovers(@NonNull View view,
@NonNull Predicate<? super MotionEvent> handled) {
checkNotNull(view, "view == null");
checkNotNull(handled, "handled == null");
return new ViewHoverObservable(view, handled);
}
/**
* Create an observable which emits on {@code view} layout changes. The emitted value is
* unspecified and should only be used as notification.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*/
@CheckResult @NonNull
public static Observable<Object> layoutChanges(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewLayoutChangeObservable(view);
}
/**
* Create an observable of layout-change events for {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*/
@CheckResult @NonNull
public static Observable<ViewLayoutChangeEvent> layoutChangeEvents(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewLayoutChangeEventObservable(view);
}
/**
* Create an observable which emits on {@code view} long-click events. The emitted value is
* unspecified and should only be used as notification.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses {@link View#setOnLongClickListener} to observe
* long clicks. Only one observable can be used for a view at a time.
*/
@CheckResult @NonNull
public static Observable<Object> longClicks(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewLongClickObservable(view, CALLABLE_ALWAYS_TRUE);
}
/**
* Create an observable which emits on {@code view} long-click events. The emitted value is
* unspecified and should only be used as notification.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses {@link View#setOnLongClickListener} to observe
* long clicks. Only one observable can be used for a view at a time.
*
* @param handled Predicate invoked each occurrence to determine the return value of the
* underlying {@link View.OnLongClickListener}.
*/
@CheckResult @NonNull
public static Observable<Object> longClicks(@NonNull View view,
@NonNull Callable<Boolean> handled) {
checkNotNull(view, "view == null");
checkNotNull(handled, "handled == null");
return new ViewLongClickObservable(view, handled);
}
/**
* Create an observable for pre-draws on {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses {@link ViewTreeObserver#addOnPreDrawListener} to
* observe pre-draws. Multiple observables can be used for a view at a time.
*/
@CheckResult @NonNull
public static Observable<Object> preDraws(@NonNull View view,
@NonNull Callable<Boolean> proceedDrawingPass) {
checkNotNull(view, "view == null");
checkNotNull(proceedDrawingPass, "proceedDrawingPass == null");
return new ViewTreeObserverPreDrawObservable(view, proceedDrawingPass);
}
/**
* Create an observable of scroll-change events for {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*/
@RequiresApi(M)
@CheckResult @NonNull
public static Observable<ViewScrollChangeEvent> scrollChangeEvents(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewScrollChangeEventObservable(view);
}
/**
* Create an observable of integers representing a new system UI visibility for {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses
* {@link View#setOnSystemUiVisibilityChangeListener} to observe system UI visibility changes.
* Only one observable can be used for a view at a time.
*/
@CheckResult @NonNull
public static Observable<Integer> systemUiVisibilityChanges(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewSystemUiVisibilityChangeObservable(view);
}
/**
* Create an observable of touch events for {@code view}.
* <p>
* <em>Warning:</em> Values emitted by this observable are <b>mutable</b> and part of a shared
* object pool and thus are <b>not safe</b> to cache or delay reading (such as by observing
* on a different thread). If you want to cache or delay reading the items emitted then you must
* map values through a function which calls {@link MotionEvent#obtain(MotionEvent)} or
* {@link MotionEvent#obtainNoHistory(MotionEvent)} to create a copy.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses {@link View#setOnTouchListener} to observe
* touches. Only one observable can be used for a view at a time.
*/
@CheckResult @NonNull
public static Observable<MotionEvent> touches(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewTouchObservable(view, PREDICATE_ALWAYS_TRUE);
}
/**
* Create an observable of touch events for {@code view}.
* <p>
* <em>Warning:</em> Values emitted by this observable are <b>mutable</b> and part of a shared
* object pool and thus are <b>not safe</b> to cache or delay reading (such as by observing
* on a different thread). If you want to cache or delay reading the items emitted then you must
* map values through a function which calls {@link MotionEvent#obtain(MotionEvent)} or
* {@link MotionEvent#obtainNoHistory(MotionEvent)} to create a copy.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <p>
* <em>Warning:</em> The created observable uses {@link View#setOnTouchListener} to observe
* touches. Only one observable can be used for a view at a time.
*
* @param handled Predicate invoked with each value to determine the return value of the
* underlying {@link View.OnTouchListener}.
*/
@CheckResult @NonNull
public static Observable<MotionEvent> touches(@NonNull View view,
@NonNull Predicate<? super MotionEvent> handled) {
checkNotNull(view, "view == null");
checkNotNull(handled, "handled == null");
return new ViewTouchObservable(view, handled);
}
/**
* Create an observable of key events for {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <em>Warning:</em> The created observable uses {@link View#setOnKeyListener} to observe
* key events. Only one observable can be used for a view at a time.
*/
@CheckResult @NonNull
public static Observable<KeyEvent> keys(@NonNull View view) {
checkNotNull(view, "view == null");
return new ViewKeyObservable(view, PREDICATE_ALWAYS_TRUE);
}
/**
* Create an observable of key events for {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
* <em>Warning:</em> The created observable uses {@link View#setOnKeyListener} to observe
* key events. Only one observable can be used for a view at a time.
*
* @param handled Predicate invoked each occurrence to determine the return value of the
* underlying {@link View.OnKeyListener}.
*/
@CheckResult @NonNull
public static Observable<KeyEvent> keys(@NonNull View view,
@NonNull Predicate<? super KeyEvent> handled) {
checkNotNull(view, "view == null");
checkNotNull(handled, "handled == null");
return new ViewKeyObservable(view, handled);
}
/**
* An action which sets the activated property of {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*/
@CheckResult @NonNull
public static Consumer<? super Boolean> activated(@NonNull final View view) {
checkNotNull(view, "view == null");
return new Consumer<Boolean>() {
@Override public void accept(Boolean value) {
view.setActivated(value);
}
};
}
/**
* An action which sets the clickable property of {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*/
@CheckResult @NonNull
public static Consumer<? super Boolean> clickable(@NonNull final View view) {
checkNotNull(view, "view == null");
return new Consumer<Boolean>() {
@Override public void accept(Boolean value) {
view.setClickable(value);
}
};
}
/**
* An action which sets the enabled property of {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*/
@CheckResult @NonNull
public static Consumer<? super Boolean> enabled(@NonNull final View view) {
checkNotNull(view, "view == null");
return new Consumer<Boolean>() {
@Override public void accept(Boolean value) {
view.setEnabled(value);
}
};
}
/**
* An action which sets the pressed property of {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*/
@CheckResult @NonNull
public static Consumer<? super Boolean> pressed(@NonNull final View view) {
checkNotNull(view, "view == null");
return new Consumer<Boolean>() {
@Override public void accept(Boolean value) {
view.setPressed(value);
}
};
}
/**
* An action which sets the selected property of {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*/
@CheckResult @NonNull
public static Consumer<? super Boolean> selected(@NonNull final View view) {
checkNotNull(view, "view == null");
return new Consumer<Boolean>() {
@Override public void accept(Boolean value) {
view.setSelected(value);
}
};
}
/**
* An action which sets the visibility property of {@code view}. {@code false} values use
* {@code View.GONE}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*/
@CheckResult @NonNull
public static Consumer<? super Boolean> visibility(@NonNull View view) {
checkNotNull(view, "view == null");
return visibility(view, View.GONE);
}
/**
* An action which sets the visibility property of {@code view}.
* <p>
* <em>Warning:</em> The created observable keeps a strong reference to {@code view}. Unsubscribe
* to free this reference.
*
* @param visibilityWhenFalse Visibility to set on a {@code false} value ({@code View.INVISIBLE}
* or {@code View.GONE}).
*/
@CheckResult @NonNull
public static Consumer<? super Boolean> visibility(@NonNull final View view,
final int visibilityWhenFalse) {
checkNotNull(view, "view == null");
if (visibilityWhenFalse == View.VISIBLE) {
throw new IllegalArgumentException(
"Setting visibility to VISIBLE when false would have no effect.");
}
if (visibilityWhenFalse != View.INVISIBLE && visibilityWhenFalse != View.GONE) {
throw new IllegalArgumentException("Must set visibility to INVISIBLE or GONE when false.");
}
return new Consumer<Boolean>() {
@Override public void accept(Boolean value) {
view.setVisibility(value ? View.VISIBLE : visibilityWhenFalse);
}
};
}
private RxView() {
throw new AssertionError("No instances.");
}
}