aboutsummaryrefslogtreecommitdiffstats
path: root/main/src/cgeo/geocaching/activity/AbstractViewPagerActivity.java
blob: 64186a060a9d68bdf30e9e80864e74ff96e50083 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
package cgeo.geocaching.activity;

import cgeo.geocaching.R;
import cgeo.geocaching.utils.Log;

import com.viewpagerindicator.TitlePageIndicator;
import com.viewpagerindicator.TitleProvider;

import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;

import android.app.Activity;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Abstract activity with the ability to manage pages in a view pager.
 *
 * @param <Page>
 *            Enum listing all available pages of this activity. The pages available at a certain point of time are
 *            defined by overriding {@link #getOrderedPages()}.
 */
public abstract class AbstractViewPagerActivity<Page extends Enum<Page>> extends AbstractActionBarActivity {

    /**
     * A {@link List} of all available pages.
     *
     * TODO Move to adapter
     */
    private final List<Page> pageOrder = new ArrayList<>();

    /**
     * Instances of all {@link PageViewCreator}.
     */
    private final Map<Page, PageViewCreator> viewCreators = new HashMap<>();

    /**
     * Store the states of the page views to be able to persist them when destroyed and reinstantiated again
     */
    private final Map<Page, Bundle> viewStates = new HashMap<>();
    /**
     * The {@link ViewPager} for this activity.
     */
    private ViewPager viewPager;

    /**
     * The {@link ViewPagerAdapter} for this activity.
     */
    private ViewPagerAdapter viewPagerAdapter;

    /**
     * The {@link TitlePageIndicator} for this activity.
     */
    private TitlePageIndicator titleIndicator;

    public interface PageViewCreator {
        /**
         * Returns a validated view.
         *
         * @return
         */
        public View getDispatchedView(final ViewGroup parentView);

        /**
         * Returns a (maybe cached) view.
         *
         * @return
         */
        public View getView(final ViewGroup parentView);

        /**
         * Handles changed data-sets.
         */
        public void notifyDataSetChanged();

        /**
         * Gets state of the view
         */
        public @Nullable
        Bundle getViewState();

        /**
         * Set the state of the view
         */
        public void setViewState(@NonNull Bundle state);
    }

    /**
     * Page selection interface for the view pager.
     *
     */
    protected interface OnPageSelectedListener {
        public void onPageSelected(int position);
    }

    /**
     * The ViewPagerAdapter for scrolling through pages of the CacheDetailActivity.
     */
    private class ViewPagerAdapter extends PagerAdapter implements TitleProvider {

        @Override
        public void destroyItem(final ViewGroup container, final int position, final Object object) {
            if (position >= pageOrder.size()) {
                return;
            }
            final Page page = pageOrder.get(position);

            // Store the state of the view if the page supports it
            final PageViewCreator creator = viewCreators.get(page);
            if (creator != null) {
                @Nullable
                final
                Bundle state = creator.getViewState();
                if (state != null) {
                    viewStates.put(page, state);
                }
            }

            container.removeView((View) object);
        }

        @Override
        public void finishUpdate(final ViewGroup container) {
        }

        @Override
        public int getCount() {
            return pageOrder.size();
        }

        @Override
        public Object instantiateItem(final ViewGroup container, final int position) {

            final Page page = pageOrder.get(position);

            PageViewCreator creator = viewCreators.get(page);

            if (null == creator && null != page) {
                creator = AbstractViewPagerActivity.this.createViewCreator(page);
                viewCreators.put(page, creator);
                viewStates.put(page, new Bundle());
            }

            View view = null;

            try {
                if (null != creator) {
                    // Result from getView() is maybe cached, but it should be valid because the
                    // creator should be informed about data-changes with notifyDataSetChanged()
                    view = creator.getView(container);

                    // Restore the state of the view if the page supports it
                    final Bundle state = viewStates.get(page);
                    if (state != null) {
                        creator.setViewState(state);
                    }

                    container.addView(view, 0);
                }
            } catch (final Exception e) {
                Log.e("ViewPagerAdapter.instantiateItem ", e);
            }
            return view;
        }

        @Override
        public boolean isViewFromObject(final View view, final Object object) {
            return view == object;
        }

        @Override
        public void restoreState(final Parcelable arg0, final ClassLoader arg1) {
        }

        @Override
        public Parcelable saveState() {
            return null;
        }

        @Override
        public void startUpdate(final ViewGroup arg0) {
        }

        @Override
        public int getItemPosition(final Object object) {
            // We are doing the caching. So pretend that the view is gone.
            // The ViewPager will get it back in instantiateItem()
            return POSITION_NONE;
        }

        @Override
        public String getTitle(final int position) {
            final Page page = pageOrder.get(position);
            if (null == page) {
                return "";
            }
            return AbstractViewPagerActivity.this.getTitle(page);
        }

    }

    /**
     * Create the view pager. Call this from the {@link Activity#onCreate} implementation.
     *
     * @param startPageIndex
     *            index of the page shown first
     * @param pageSelectedListener
     *            page selection listener or <code>null</code>
     */
    protected final void createViewPager(final int startPageIndex, final OnPageSelectedListener pageSelectedListener) {
        // initialize ViewPager
        viewPager = (ViewPager) findViewById(R.id.viewpager);
        viewPagerAdapter = new ViewPagerAdapter();
        viewPager.setAdapter(viewPagerAdapter);

        titleIndicator = (TitlePageIndicator) findViewById(R.id.pager_indicator);
        titleIndicator.setViewPager(viewPager);
        if (pageSelectedListener != null) {
            titleIndicator.setOnPageChangeListener(new OnPageChangeListener() {
                @Override
                public void onPageSelected(final int position) {
                    pageSelectedListener.onPageSelected(position);
                }

                @Override
                public void onPageScrolled(final int position, final float positionOffset, final int positionOffsetPixels) {
                }

                @Override
                public void onPageScrollStateChanged(final int state) {
                }
            });
        }

        // switch to entry page (last used or 2)
        if (viewPagerAdapter.getCount() < startPageIndex) {
            for (int i = 0; i <= startPageIndex; i++) {
                // we can't switch to a page that is out of bounds, so we add null-pages
                pageOrder.add(null);
            }
        }
        viewPagerAdapter.notifyDataSetChanged();
        viewPager.setCurrentItem(startPageIndex, false);
    }

    /**
     * create the view creator for the given page
     *
     * @return new view creator
     */
    protected abstract PageViewCreator createViewCreator(Page page);

    /**
     * get the title for the given page
     */
    protected abstract String getTitle(Page page);

    protected final void reinitializeViewPager() {

        // notify all creators that the data has changed
        for (final PageViewCreator creator : viewCreators.values()) {
            creator.notifyDataSetChanged();
        }
        // reset the stored view states of all pages
        for (final Bundle state : viewStates.values()) {
            state.clear();
        }

        pageOrder.clear();
        final Pair<List<? extends Page>, Integer> pagesAndIndex = getOrderedPages();
        pageOrder.addAll(pagesAndIndex.getLeft());

        // Since we just added pages notifyDataSetChanged needs to be called before we possibly setCurrentItem below.
        //  But, calling it will reset current item and we won't be able to tell if we would have been out of bounds
        final int currentItem = getCurrentItem();

        // notify the adapter that the data has changed
        viewPagerAdapter.notifyDataSetChanged();

        // switch to details page, if we're out of bounds
        final int defaultPage = pagesAndIndex.getRight();
        if (currentItem < 0 || currentItem >= viewPagerAdapter.getCount()) {
            viewPager.setCurrentItem(defaultPage, false);
        }

        // notify the indicator that the data has changed
        titleIndicator.notifyDataSetChanged();
    }

    /**
     * @return the currently available list of ordered pages, together with the index of the default page
     */
    protected abstract Pair<List<? extends Page>, Integer> getOrderedPages();

    public final Page getPage(final int position) {
        return pageOrder.get(position);
    }

    protected final int getPageIndex(final Page page) {
        return pageOrder.indexOf(page);
    }

    protected final PageViewCreator getViewCreator(final Page page) {
        return viewCreators.get(page);
    }

    protected final boolean isCurrentPage(final Page page) {
        return getCurrentItem() == getPageIndex(page);
    }

    protected int getCurrentItem() {
        return viewPager.getCurrentItem();
    }
}