Back to project page InfinitePager.
The source code is released under:
Apache License
If you think the Android project InfinitePager listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* * Copyright 2013 Adam Parr//from w w w .jav a2 s .c o m * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.paradam.infinitepager; import android.support.v4.view.PagerAdapter; import android.view.ViewGroup; /** * <p>This class extends the support class of {@link android.support.v4.view.PagerAdapter}, as such * most of the information for that class is also true for this class and sub-classes with only a * couple of changes.<p/> * * <p>Base class providing the adapter to populate pages inside of a {@link com.github.paradam.infinitepager.InfiniteViewPager}. * You will most likely want to use a more specific implementation of this, such as for use with APIs * of 13 or above {@link InfiniteFragmentPagerAdapter} or {@link InfiniteFragmentStatePagerAdapter}. * For APIs 4 and above or using the {@link android.support.v4.app.Fragment Support Fragment} class, * use {@link com.github.paradam.support.v4.infinitepager.InfiniteFragmentPagerAdapter} or * {@link com.github.paradam.support.v4.infinitepager.InfiniteFragmentStatePagerAdapter}</p> * * <p>When you implement an InfinitePagerAdapter, you must override the following methods at minimum:</p> * * <ul> * <li>{@link #instantiateRelativeItem(android.view.ViewGroup, int)}</li> * <li>{@link #destroyRelativeItem(android.view.ViewGroup, int, Object)}</li> * <li>{@link #getRelativeCount()}</li> * <li>{@link #isViewFromObject(android.view.View, Object)}</li> * </ul> * * <p>InfinitePagerAdapter is more general than the adapters used for {@link android.widget.AdapterView AdapterViews}. * Instead of providing a View recycling mechanism directly InfiniteViewPager uses callbacks to * indicate the steps taken during an update. An InfinitePagerAdapter may implement a form of View * recycling if desired or use a more sophisticated method of managing page Views such as Fragment * transactions where each page is represented by its own Fragment.</p> * * <p>InfiniteViewPager associates each page with a key Object instead of working with Views directly. * This key is used to track and uniquely identify a given page independent of its position in the * adapter. A call to the InfinitePagerAdapter method {@link #startUpdate(android.view.ViewGroup)} * indicates that the contents of the InfiniteViewPager are about to change. One or more calls to * {@link #instantiateRelativeItem(android.view.ViewGroup, int)} and/or * {@link #destroyRelativeItem(android.view.ViewGroup, int, Object)} will follow, and the end of an * update will be signalled by a call to {@link #finishUpdate(android.view.ViewGroup)}. By the time * {@link #finishUpdate(android.view.ViewGroup) finishUpdate} returns the views associated with the * key objects returned by {@link #instantiateRelativeItem(android.view.ViewGroup, int) instantiateRelativeItem} * should be added to the parent ViewGroup passed to these methods and the views associated with the * keys passed to {@link #destroyRelativeItem(android.view.ViewGroup, int, Object) destroyRelativeItem} * should be removed. The method {@link #isViewFromObject(android.view.View, Object)} identifies * whether a page View is associated with a given key object.</p> * * <p>A very simple InfinitePagerAdapter may choose to use the page Views themselves as key objects, * returning them from {@link #instantiateRelativeItem(android.view.ViewGroup, int)} after creation * and adding them to the parent ViewGroup. A matching {@link #destroyRelativeItem(android.view.ViewGroup, int, Object)} * implementation would remove the View from the parent ViewGroup and * {@link #isViewFromObject(android.view.View, Object)} could be implemented as <code>return view == object;</code>.</p> * * <p>InfinitePagerAdapter supports data set changes. Data set changes must occur on the main thread * and must end with a call to {@link #notifyDataSetChanged()} similar to AdapterView adapters * derived from {@link android.widget.BaseAdapter}. A data set change may involve pages being added, * removed, or changing position. The InfiniteViewPager will keep the current page active provided * the adapter implements the method {@link #getRelativeItemPosition(Object)}.</p> * * <p>Sub-classes that do override any of the methods found within {@link android.support.v4.view.PagerAdapter} * should call through to the super method where possible or failing that, call through to that methods * associated ...Relative... method, using {@link #getRelativePosition(int)} to adjust the position * given to the current method to the value the associated Relative method is expecting.</p> * * <p>A general rule an be used to determine how the interaction to this class or any sub-classes * should occur. If the interacting class is expecting an InfinitePagerAdapter, use the * ...Relative...() methods in place of the similarly named methods from PagerAdapter class. If the * class is expecting any PagerAdapter class (sub-classes should use this method), use the methods * accessible from the PagerAdapter class.</p> * * <p>While an InfinitePagerAdapter can be used within any ViewPager class, if it is not used within * an InfiniteViewPager class it will not behave as an infinitely scrollable list, but instead as a * normal PagerAdapter. For the Adapter to behave as an infinitely scrollable list there needs to be * at least {@value #MIN} items, fewer and it will behave as a normal PagerAdapter</p> * * @author Adam Parr */ public abstract class InfinitePagerAdapter extends PagerAdapter { /** * <p>The number of additional pages on either side of the list of pages to simulate a infinite * scrolling action. This is the internal offset to which the page at index position <tt>0</tt> * is internally positioned within the PagerAdapter. <p/> * * <p>In essence <code>getRelativeItem(i)</code> is the same as <code>getItem(i+getMargin())</code>.</p> */ public static final int MARGIN = 2; /** * The minimum number of pages needed before the PagerAdapter will behave as an * InfinitePagerAdapter. */ public static final int MIN = 4; /** * A page other than the first or last is the primary item. */ private static final int SWITCHER_OTHER = 0; /** * The first page is the primary item. */ private static final int SWITCHER_FIRST = 1; /** * The last page is the primary item. */ private static final int SWITCHER_LAST = 2; /** * The InfinitePagerAdapter has not yet been attached to a ViewPager yet. */ private static final int NOT_SET = -1; /** * The ViewPager the InfinitePagerAdapter is attached to is not an InfiniteViewPager. Do not try * to allow the ViewPager to be infinitely scrollable. */ private static final int NORMAL_ADAPTER = 0; /** * The ViewPager the InfinitePagerAdapter is attached to is an InfiniteViewPager. Allow the * ViewPager to scroll infinitely. */ private static final int INFINITE_ADAPTER = 1; /** * The number of additional pages per side to simulate. */ private int margin = MARGIN; /** * The current relative size of the PagerAdapter */ private int mCount = -1; /** * {@link #NOT_SET} is not set, {@link #INFINITE_ADAPTER} if the ViewPager as provided through * setPrimaryItem(ViewGroup, int Object) is in fact an InfiniteViewPager. {@link #NORMAL_ADAPTER} * otherwise. */ private int attachedToInfiniteViewPager = NOT_SET; /** * The state of the Switcher. * * @see #SWITCHER_OTHER * @see #SWITCHER_FIRST * @see #SWITCHER_LAST */ private int switcher = SWITCHER_OTHER; /** * Get the title of the Page at the given position. * * @param position The position to get the title of. * @return A CharSequence of the title of the Page. */ public CharSequence getRelativePageTitle(int position) { return null; } /** * <p>Called when the host view is attempting to determine if an item's position has changed. * Returns {@link #POSITION_UNCHANGED} if the position of the given item has not changed or * {@link #POSITION_NONE} if the item is no longer present in the adapter.<p/> * * <p>The default implementation assumes that items will never change position and always * returns {@link #POSITION_UNCHANGED}.</p> * * @param object Object representing an item, previously returned by a call to {@link #instantiateItem(android.view.View, int)}. * @return object's new position index from [0, {@link #getRelativeCount()}], {@link #POSITION_UNCHANGED} * if the object's position has not changed, or {@link #POSITION_NONE} if the item is no longer present. */ public int getRelativeItemPosition(Object object) { return POSITION_UNCHANGED; } /** * Get the actual number of pages in this adapter. * * @return The actual number of pages in this adapter. */ public abstract int getRelativeCount(); /** * Create the page for the given position. The adapter is responsible for adding the view to * the container given here, although it only must ensure this is done by the time it returns * from {@link #finishUpdate(android.view.ViewGroup)}. * * @param container The containing View in which the page will be shown. * @param position The page position to be instantiated. * @return Returns an Object representing the new page. This does not need to be a View, but * can be some other container of the page. */ public abstract Object instantiateRelativeItem(ViewGroup container, int position); /** * Remove a page for the given position. The adapter is responsible for removing the view from * its container, although it only must ensure this is done by the time it returns from {@link * #finishUpdate(android.view.ViewGroup)}. * * @param container The containing View from which the page will be removed. * @param position The page position to be removed. * @param object The same object that was returned by {@link #instantiateRelativeItem(android.view.ViewGroup, * int)}. */ public abstract void destroyRelativeItem(ViewGroup container, int position, Object object); /** * Called to inform the adapter of which item is currently considered to be the "primary", that * is the one show to the user as the current page. * * @param container The containing View from which the page will be removed. * @param position The page position that is now the primary. * @param object The same object that was returned by {@link #instantiateRelativeItem(android.view.ViewGroup, * int)}. */ public void setRelativePrimaryItem(ViewGroup container, int position, Object object) { } /** * If true, {@link #onPreNotifyDataSetChange()} has been called. */ private boolean preNotifyCalled = false; /** * <p>Update the count the number of pages in the PagerAdapter.<p/> * * <p>Call this method before calling the super method {@link #notifyDataSetChanged()} if the * implementing class needs an updated value for the number of items or the appropriate value of * {@link #margin} as returned by {@link #getMargin()}. If this method is not called, then the * relevant functions will be completed when calling the super implementation of * {@link #notifyDataSetChanged()}.</p> */ protected void onPreNotifyDataSetChange() { setCount(getRelativeCount()); preNotifyCalled = true; } @Override public void notifyDataSetChanged() { if (!preNotifyCalled) { onPreNotifyDataSetChange(); } super.notifyDataSetChanged(); preNotifyCalled = false; } /** * <p>The number of pages in this adapter including the duplicate leading and ending pages at * either side of the list to simulate an infinite scrolling list of pages.<p/> * * <p>Use {@link #getRelativeCount()} to get the count managed by sub-classes.</p> * * @see #getRelativeCount() */ @Override public int getCount() { if (mCount <= 0) { setCount(getRelativeCount()); // notifyDataSetChanged(); } return mCount + (margin * 2); } /** * <p>Returns the absolute position of object, or {@link #POSITION_NONE} if the reported position * of the object as returned by {@link #getRelativeItemPosition(Object)} is {@link #POSITION_NONE}.</p> * * <p>Use {@link #getRelativeItemPosition(Object)} to get the position expected by sub-classes.</p> * * @param object Object representing an item, previously returned by a call to {@link #instantiateItem(android.view.View, int)}. * @return object's new position index from [0, {@link #getCount()}], {@link #POSITION_UNCHANGED} * if the object's position has not changed, or {@link #POSITION_NONE} if the item is no longer present. * * @see #getRelativeItemPosition(Object) */ @Override public int getItemPosition(Object object) { if (object == null) { return POSITION_NONE; } int relativeObjectPosition = getRelativeItemPosition(object); // The position of this Object. int returnedPosition = POSITION_NONE; if (relativeObjectPosition == POSITION_UNCHANGED) { returnedPosition = POSITION_UNCHANGED; } else if (relativeObjectPosition != POSITION_NONE) { // The items position has changed, return the new position returnedPosition = relativeObjectPosition + margin; } return returnedPosition; } /** * <p>Called to inform the adapter of which item is currently considered to be the "primary", that * is the one show to the user as the current page. <p/> * * <p>Use {@link #setRelativePrimaryItem(android.view.ViewGroup, int, Object)} to get the count * expected by sub-classes.</p> * * @param container The containing View from which the page will be removed. * @param position The page position that is now the primary. * @param object The same object that was returned by {@link #instantiateItem(android.view.View, int)}. * * @see #setRelativePrimaryItem(android.view.ViewGroup, int, Object) */ @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { setRelativePrimaryItem(container, getRelativePosition(position), object); if (attachedToInfiniteViewPager == NOT_SET) { attachedToInfiniteViewPager = container instanceof InfiniteViewPager ? INFINITE_ADAPTER : NORMAL_ADAPTER; setCount(mCount); } if (position == mCount + margin || position == margin) { // Page is first page switcher = SWITCHER_FIRST; } else if (position == margin - 1 || position == mCount + margin - 1) { // Page is last page switcher = SWITCHER_LAST; } else { switcher = SWITCHER_OTHER; } } /** * <p>Create the page for the given position. The adapter is responsible for adding the view to * the container given here, although it only must ensure this is done by the time it returns * from {@link #finishUpdate(android.view.ViewGroup)}. <p/> * * <p>Use {@link #instantiateRelativeItem(android.view.ViewGroup, int) to instantiate the correct * Object as expected by sub-classes.</p> * * @param container The containing View in which the page will be shown. * @param position The page position to be instantiated. * @return Returns an Object representing the new page. This does not need to be a View, but * can be some other container of the page. * * @see #instantiateRelativeItem(ViewGroup, int) */ @Override public Object instantiateItem(ViewGroup container, int position) { return instantiateRelativeItem(container, getRelativePosition(position)); } /** * <p>Remove a page for the given position. The adapter is responsible for removing the view from * its container, although it only must ensure this is done by the time it returns from {@link * #finishUpdate(android.view.ViewGroup)}.<p/> * * <p>Use {@link #destroyRelativeItem(android.view.ViewGroup, int, Object) to destroy the correct * Object as expected by sub-classes.</p> * * @param container The containing View from which the page will be removed. * @param position The page position to be removed. * @param object The same object that was returned by {@link #instantiateItem(android.view.View, int)}. * * @see #destroyRelativeItem(ViewGroup, int, Object) */ @Override public void destroyItem(ViewGroup container, int position, Object object) { int last = mCount + margin - 1; if (position >= margin - 1 && position <= last + 1) { // First Item (End) Last Item (End) Last Item (Start) First Item (Start) if (margin > 0 && (((position == mCount + margin || position == last) && switcher == SWITCHER_FIRST) || ((position == margin - 1 || position == margin) && switcher == SWITCHER_LAST))) { // This object is beside the current active object, it should not be removed. return; } if (switcher == SWITCHER_FIRST && margin == 0 && position == 1) { // Case where the item at the index position 1 may be requested to be destroyed // even when the current selected item is at position 0, ignore this request. return; } destroyRelativeItem(container, getRelativePosition(position), object); } } /** * <p>Get the title of the Page at the given position. <p/> * * <p>Use {@link #getRelativePageTitle(int) to get the correct Object title as expected by sub-classes.</p> * * @param position The position to get the title of. * @return A CharSequence of the title of the Page. * * @see #getRelativePageTitle(int) */ @Override public CharSequence getPageTitle(int position) { return getRelativePageTitle(getRelativePosition(position)); } /** * Returns the proportional width of a given page as a percentage of the ViewPager's measured * width from (0.f-1.f] * * @param position The position of the page requested * @return Proportional width for the given page position * @deprecated Having a width different from the default causes problems at the edges in an * InfiniteViewPager when trying to switch to the correct Page. */ @Override public final float getPageWidth(int position) { return super.getPageWidth(position); } /** * Set the number of pages the PagerAdapter has initially. * * @param count The number of pages. */ private void setCount(int count) { mCount = count; if (mCount < MIN || attachedToInfiniteViewPager == NORMAL_ADAPTER) { margin = 0; } else { margin = MARGIN; } } /** * <p>The internal margin on either side of the real pages. <p/> * * <p>Only sub-classes that directly extend {@link com.github.paradam.infinitepager.InfinitePagerAdapter} * need to use this method.</p> * * @return An integer of either 0 or 2; */ protected int getMargin() { return margin; } /** * <p>Return The actual position of the page as implemented by the sub-class. <p/> * * <p>Only sub-classes that directly extend InfinitePagerAdapter need to use this method.</p> * * @param position The position to normalise. * @return The actual position of the page as implemented by the sub-class. */ protected int getRelativePosition(int position) { return (position - margin + mCount) % mCount; } }