Skip to content

Commit bc22439

Browse files
author
Hamid Palo
committed
Add ViewObservables.listViewScroll(AbsListView).
1 parent d630d00 commit bc22439

File tree

5 files changed

+259
-19
lines changed

5 files changed

+259
-19
lines changed
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package rx.android.events;
16+
17+
import android.widget.AbsListView;
18+
19+
public class OnListViewScrollEvent {
20+
public final AbsListView listView;
21+
public final int scrollState;
22+
public final int firstVisibleItem;
23+
public final int visibleItemCount;
24+
public final int totalItemCount;
25+
26+
public OnListViewScrollEvent(
27+
AbsListView listView, int scrollState, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
28+
this.listView = listView;
29+
this.scrollState = scrollState;
30+
this.firstVisibleItem = firstVisibleItem;
31+
this.visibleItemCount = visibleItemCount;
32+
this.totalItemCount = totalItemCount;
33+
}
34+
35+
@Override
36+
public boolean equals(Object o) {
37+
if (this == o) {
38+
return true;
39+
}
40+
if (o == null || getClass() != o.getClass()) {
41+
return false;
42+
}
43+
44+
OnListViewScrollEvent that = (OnListViewScrollEvent) o;
45+
46+
if (firstVisibleItem != that.firstVisibleItem) {
47+
return false;
48+
}
49+
if (scrollState != that.scrollState) {
50+
return false;
51+
}
52+
if (totalItemCount != that.totalItemCount) {
53+
return false;
54+
}
55+
if (visibleItemCount != that.visibleItemCount) {
56+
return false;
57+
}
58+
if (!listView.equals(that.listView)) {
59+
return false;
60+
}
61+
62+
return true;
63+
}
64+
65+
@Override
66+
public int hashCode() {
67+
int result = listView.hashCode();
68+
result = 31 * result + scrollState;
69+
result = 31 * result + firstVisibleItem;
70+
result = 31 * result + visibleItemCount;
71+
result = 31 * result + totalItemCount;
72+
return result;
73+
}
74+
75+
@Override
76+
public String toString() {
77+
return "OnListViewScrollEvent{" +
78+
"listView=" + listView +
79+
", scrollState=" + scrollState +
80+
", firstVisibleItem=" + firstVisibleItem +
81+
", visibleItemCount=" + visibleItemCount +
82+
", totalItemCount=" + totalItemCount +
83+
'}';
84+
}
85+
}

rxandroid/src/main/java/rx/android/observables/ViewObservable.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package rx.android.observables;
1515

1616
import android.view.View;
17+
import android.widget.AbsListView;
1718
import android.widget.AdapterView;
1819
import android.widget.CompoundButton;
1920
import android.widget.TextView;
@@ -22,9 +23,11 @@
2223
import rx.android.events.OnCheckedChangeEvent;
2324
import rx.android.events.OnClickEvent;
2425
import rx.android.events.OnItemClickEvent;
26+
import rx.android.events.OnListViewScrollEvent;
2527
import rx.android.events.OnTextChangeEvent;
2628
import rx.android.operators.OperatorAdapterViewOnItemClick;
2729
import rx.android.operators.OperatorCompoundButtonInput;
30+
import rx.android.operators.OnSubscribeListViewScroll;
2831
import rx.android.operators.OperatorTextViewInput;
2932
import rx.android.operators.OperatorViewClick;
3033

@@ -58,4 +61,13 @@ public static Observable<OnItemClickEvent> itemClicks(final AdapterView<?> adapt
5861
return Observable.create(new OperatorAdapterViewOnItemClick(adapterView));
5962
}
6063

64+
/**
65+
* Returns an observable that emits all the scroll events from the provided ListView.
66+
* Note that this will replace any listeners previously set through
67+
* {@link android.widget.AbsListView#setOnScrollListener(android.widget.AbsListView.OnScrollListener)} unless those
68+
* were set by this method or {@link rx.android.operators.OnSubscribeListViewScroll}.
69+
*/
70+
public static Observable<OnListViewScrollEvent> listScrollEvents(final AbsListView listView) {
71+
return Observable.create(new OnSubscribeListViewScroll(listView));
72+
}
6173
}
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/**
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package rx.android.operators;
16+
17+
import android.widget.AbsListView;
18+
import android.widget.AdapterView;
19+
import rx.Observable;
20+
import rx.Subscriber;
21+
import rx.android.events.OnListViewScrollEvent;
22+
import rx.android.observables.Assertions;
23+
import rx.android.subscriptions.AndroidSubscriptions;
24+
import rx.functions.Action0;
25+
26+
import java.util.ArrayList;
27+
import java.util.List;
28+
import java.util.Map;
29+
import java.util.WeakHashMap;
30+
31+
public class OnSubscribeListViewScroll implements Observable.OnSubscribe<OnListViewScrollEvent> {
32+
33+
private final AbsListView listView;
34+
35+
public OnSubscribeListViewScroll(AbsListView listView) {
36+
this.listView = listView;
37+
}
38+
39+
@Override
40+
public void call(final Subscriber<? super OnListViewScrollEvent> observer) {
41+
Assertions.assertUiThread();
42+
43+
final CompositeOnScrollListener composite = CachedListeners.getFromViewOrCreate(listView);
44+
final AbsListView.OnScrollListener listener = new AbsListView.OnScrollListener() {
45+
int currentScrollState = SCROLL_STATE_IDLE;
46+
47+
@Override
48+
public void onScrollStateChanged(AbsListView view, int scrollState) {
49+
this.currentScrollState = scrollState;
50+
}
51+
52+
@Override
53+
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
54+
OnListViewScrollEvent event = new OnListViewScrollEvent(view, this.currentScrollState, firstVisibleItem,
55+
visibleItemCount, totalItemCount);
56+
observer.onNext(event);
57+
}
58+
};
59+
60+
composite.addOnScrollListener(listener);
61+
observer.add(AndroidSubscriptions.unsubscribeInUiThread(new Action0() {
62+
@Override
63+
public void call() {
64+
composite.removeOnScrollListener(listener);
65+
}
66+
}));
67+
}
68+
69+
private static class CompositeOnScrollListener implements AbsListView.OnScrollListener {
70+
71+
private final List<AbsListView.OnScrollListener> listeners = new ArrayList<AbsListView.OnScrollListener>();
72+
73+
public boolean addOnScrollListener(final AbsListView.OnScrollListener listener) {
74+
return listeners.add(listener);
75+
}
76+
77+
public boolean removeOnScrollListener(final AbsListView.OnScrollListener listener) {
78+
return listeners.remove(listener);
79+
}
80+
81+
@Override
82+
public void onScrollStateChanged(AbsListView view, int scrollState) {
83+
for (AbsListView.OnScrollListener listener : listeners) {
84+
listener.onScrollStateChanged(view, scrollState);
85+
}
86+
}
87+
88+
@Override
89+
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
90+
for (AbsListView.OnScrollListener listener : listeners) {
91+
listener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
92+
}
93+
}
94+
}
95+
96+
private static class CachedListeners {
97+
98+
private static final Map<AdapterView<?>, CompositeOnScrollListener> sCachedListeners =
99+
new WeakHashMap<AdapterView<?>, CompositeOnScrollListener>();
100+
101+
public static CompositeOnScrollListener getFromViewOrCreate(final AbsListView view) {
102+
final CompositeOnScrollListener cached = sCachedListeners.get(view);
103+
if (cached != null) {
104+
return cached;
105+
}
106+
107+
final CompositeOnScrollListener listener = new CompositeOnScrollListener();
108+
109+
sCachedListeners.put(view, listener);
110+
view.setOnScrollListener(listener);
111+
112+
return listener;
113+
}
114+
}
115+
}

sample-app/src/main/java/rx/android/samples/ListFragmentActivity.java

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
package rx.android.samples;
22

33
import android.app.Activity;
4-
import android.app.ListFragment;
4+
import android.app.Fragment;
55
import android.os.Bundle;
6+
import android.view.LayoutInflater;
7+
import android.view.View;
8+
import android.view.ViewGroup;
69
import android.widget.ArrayAdapter;
7-
10+
import android.widget.ListView;
11+
import android.widget.ProgressBar;
812
import rx.Observable;
913
import rx.Subscriber;
14+
import rx.android.events.OnListViewScrollEvent;
15+
import rx.android.observables.AndroidObservable;
16+
import rx.android.observables.ViewObservable;
17+
import rx.functions.Action1;
1018

1119
import static rx.android.schedulers.AndroidSchedulers.mainThread;
1220

@@ -30,7 +38,7 @@ protected void onCreate(Bundle savedInstanceState) {
3038
}
3139

3240
@SuppressWarnings("ConstantConditions")
33-
public static class RetainedListFragment extends ListFragment {
41+
public static class RetainedListFragment extends Fragment {
3442

3543
private ArrayAdapter<String> adapter;
3644

@@ -39,18 +47,38 @@ public RetainedListFragment() {
3947
}
4048

4149
@Override
42-
public void onCreate(Bundle savedInstanceState) {
43-
super.onCreate(savedInstanceState);
50+
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
51+
View view = inflater.inflate(R.layout.list_fragment, container, false);
4452

4553
adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1);
46-
setListAdapter(adapter);
47-
SampleObservables.numberStrings(1, 20, 250)
48-
.observeOn(mainThread())
49-
.lift(new BindAdapter())
50-
.subscribe();
54+
ListView listView = (ListView) view.findViewById(android.R.id.list);
55+
listView.setAdapter(adapter);
56+
57+
AndroidObservable.bindFragment(this, SampleObservables.numberStrings(1, 500, 100))
58+
.observeOn(mainThread())
59+
.lift(new BindAdapter())
60+
.subscribe();
61+
62+
final ProgressBar progressBar = (ProgressBar) view.findViewById(android.R.id.progress);
63+
AndroidObservable.bindFragment(this, ViewObservable.listScrollEvents(listView))
64+
.subscribe(new Action1<OnListViewScrollEvent>() {
65+
@Override
66+
public void call(OnListViewScrollEvent event) {
67+
if (event.totalItemCount == 0) {
68+
return;
69+
}
70+
71+
int progress =
72+
(int) ((100.0 * (event.firstVisibleItem + event.visibleItemCount)) / event.totalItemCount);
73+
progressBar.setProgress(progress);
74+
}
75+
});
76+
77+
return view;
5178
}
5279

5380
private final class BindAdapter implements Observable.Operator<String, String> {
81+
5482
@Override
5583
public Subscriber<? super String> call(Subscriber<? super String> subscriber) {
5684
return new Subscriber<String>() {
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
1-
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
1+
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
22
android:layout_width="match_parent"
33
android:layout_height="match_parent">
44

5-
<TextView
6-
android:id="@android:id/empty"
5+
<ProgressBar
6+
android:id="@android:id/progress"
7+
style="@android:style/Widget.Holo.ProgressBar.Horizontal"
78
android:layout_width="match_parent"
8-
android:layout_height="match_parent"
9-
android:gravity="center"
10-
android:text="Loading..."
11-
android:visibility="gone" />
9+
android:layout_height="48dp"
10+
android:indeterminate="false" />
1211

1312
<ListView
1413
android:id="@android:id/list"
1514
android:layout_width="match_parent"
16-
android:layout_height="match_parent" />
15+
android:layout_height="match_parent"
16+
android:layout_marginTop="48dp" />
1717

18-
</RelativeLayout>
18+
</FrameLayout>

0 commit comments

Comments
 (0)