A ViewPager and a PagerAdapter that can:
- AutoScroll (On/Off able)
- Infinite Loop (On/Off able)
- ViewPager's height can be wrap_content / an aspect ratio
- Adjustable auto scroll interval
- Won't scroll nor loop if there is only 1 item
- Works well with notifyDataSetChanged()
- Supports page indicators
- Supports different view types
- (New! 1.2.0) Support peeking adjacent items (But first and last item will appear only after scroll state is idle)
Although there are already quite a number of similar libraries out there,
I cannot find one that fits all of the below requirements:
- Sufficient documentation
- Last updated in less than 3 years
- Good infinite looping effect
- Configurable auto-scroll
- ViewPager that supports fixed aspect ratio
- Good support with Page Indicators
Especially for 6, even some of them supports, they provide built-in indicators only; or does not tell user how to implement their own indicator.
I wrote this library to tackle all of these problems I faced after trying a whole day with other libraries.
First make sure jcenter()
is included as a repository in your project's build.gradle:
allprojects {
repositories {
jcenter()
}
}
And then add the below to your app's build.gradle:
implementation 'com.asksira.android:loopingviewpager:1.2.0'
<com.asksira.loopingviewpager.LoopingViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:isInfinite="true"
app:autoScroll="true"
app:scrollInterval="5000"
android:clipToPadding="false"
app:viewpagerAspectRatio="1.78"
app:itemAspectRatio="1.33"/>
Attribute Name | Default | Allowed Values |
---|---|---|
isInfinite | false | true / false |
autoScroll | false | true / false |
viewpagerAspectRatio | 0 | any float (width / height) |
wrap_content(deprecated) | true | true / false |
scrollInterval | 5000 | any integer (represents ms) |
itemAspectRatio | 0 | any float (width / height) |
viewpagerAspectRatio 0 means does not apply aspectRatio.
That means, default LoopingViewPager has no aspect ratio and wrap_content is true.
Once aspect ratio is set, wrap_content will be overrided (meaningless).
In most cases, you should set an aspect ratio.
itemAspectRatio
is the aspectRatio of the the item. So, if itemAspectRatio is higher than viewpagerAspectRatio, you can use clipToPadding="false"
to create a peek item effect.
You can refer to #17.
If you wonder why you need to set app:wrap_content="true"
, take a look at this Stackoverflow post.
wrap_content is deprecated in v1.1.3. It is still available but I think there can be unknown problems. I am not going to debug issues related to setting wrap_content to true as well.
You should
- Specify the data type in the generic (
<DataType>
) - Create your own constructor according to this
DataType
- override
inflateView()
andbindView()
- DO NOT override getCount(). Look at
LoopingPagerAdapter
. getCount() has special implementation.
public class DemoInfiniteAdapter extends LoopingPagerAdapter<Integer> {
public DemoInfiniteAdapter(Context context, ArrayList<Integer> itemList, boolean isInfinite) {
super(context, itemList, isInfinite);
}
//This method will be triggered if the item View has not been inflated before.
@Override
protected View inflateView(int viewType, ViewGroup container, int listPosition) {
return LayoutInflater.from(context).inflate(R.layout.item_pager, container, false);
}
//Bind your data with your item View here.
//Below is just an example in the demo app.
//You can assume convertView will not be null here.
//You may also consider using a ViewHolder pattern.
@Override
protected void bindView(View convertView, int listPosition, int viewType) {
convertView.findViewById(R.id.image).setBackgroundColor(context.getResources().getColor(getBackgroundColor(listPosition)));
TextView description = convertView.findViewById(R.id.description);
description.setText(String.valueOf(itemList.get(listPosition)));
}
}
adapter = new DemoInfiniteAdapter(context, dataItems, true);
viewPager.setAdapter(adapter);
@Override
protected void onResume() {
super.onResume();
viewPager.resumeAutoScroll();
}
@Override
protected void onPause() {
viewPager.pauseAutoScroll();
super.onPause();
}
If you have new data to update to your adapter, simply call:
adapter.setItemList(newItemList);
viewPager.reset(); //In order to reset the current page position
Simple! Override one more method in your Adapter:
@Override
protected int getItemViewType(int listPosition) {
//Return your own view type, same as what you did when using RecyclerView
}
And then, of course, according to the viewtype
parameter passed to you in inflateView()
and bindView()
, differentiate what you need to inflate or bind.
You may also refer to the demo app for a complete example.
I don't provide a built-in page indicator because:
- ViewPager and Indicator are logically separated
- I want to make this library adaptable to all page indicators
With that said, I personally suggest using this PageIndicatorView.
I create this demo and tested using this library.
There are 2 callbacks in LoopingViewPager
that are designed to tell a PageIndicator 2 things:
- I am now being scrolled to a new page, please update your indicator transition position;
- I am now being selected to a new page, please update your indicator selected position.
And a public method getIndicatorCount()
that can tell the indicator how many indicators(dots) should it show.
And here is an example using PageIndicatorView:
//Do not bind IndicatorView directly with ViewPager.
//Below is how we achieve the effect by binding manually.
//Tell the IndicatorView that how many indicators should it display:
indicatorView.setCount(viewPager.getIndicatorCount());
//Set IndicatorPageChangeListener on LoopingViewPager.
//When the methods are called, update the Indicator accordingly.
viewPager.setIndicatorPageChangeListener(new LoopingViewPager.IndicatorPageChangeListener() {
@Override
public void onIndicatorProgress(int selectingPosition, float progress) {
}
@Override
public void onIndicatorPageChange(int newIndicatorPosition) {
indicatorView.setSelection(newIndicatorPosition);
}
});
Don't forget to update the indicator counts if you updated items in adapter:
indicatorView.setCount(viewPager.getIndicatorCount());
By implementing this way, you can basically use any indicators you like, as long as that indicator allows you to configure programmatically (1) The number of indicators; (2) Which indicator is selected. And even, if it supports, (3) The progress of indicator transition effect.
You may have already noticed, there is an interactive effect - where the indicator position follows the swipe action of user, as shown in the second GIF in the demo section.
If you want to do this, use the below instead:
viewPager.setIndicatorPageChangeListener(new LoopingViewPager.IndicatorPageChangeListener() {
@Override
public void onIndicatorProgress(int selectingPosition, float progress) {
indicatorView.setProgress(selectingPosition, progress);
}
@Override
public void onIndicatorPageChange(int newIndicatorPosition) {
}
});
In my demo, I am using the PageIndicatorView.
This PageIndicatorView expects you to handle the indicator progress solely by yourself, i.e. The indicator will not finish its animation if you call setSelection() on it.
e.g. I scrolled to 50% from page 1 to page 2. Then I released by finger. PageIndicatorView
expects you to continue calling setProgress()
from 0.5, 0.6, all the way to 0.99, 1.0; instead of calling setCurrentItem()
the moment you released your finger.
I call this type of indicator non-smart. Default handling of LoopingViewPager treats indicator as non-smart.
If you have another IndicatorView
which is smart (i.e. It will finish the animation by itself if you call setSelection()
the moment user released his finger), do the following:
viewPager.setIndicatorSmart(true);
By setting this, LoopingViewPager
will call onIndicatorProgress()
only when user is dragging, but not after he released his finger.
Therefore you should call indicatorView.setSelection(newIndicatorPosition)
in onIndicatorPageChange()
.
However, I have to warn you that, up to the current release, the effect of onIndicatorProgress()
is still imperfect on non-smart indicators.
As far as I can find out, I noticed the below problems:
- If user swipes very fastly, i.e. before
ViewPager
reachesSCROLL_STATE_IDLE
, directly fromSCROLL_STATE_SETTLING
toSCROLL_STATE_DRAGGING
, the indicator will not move until user released his finger again. This is not obvious unless user is attempting to test this indicator effect. - If user skip pages very fastly, e.g. from page 1 to page 6 and then to page 3 quickly, the indicator may appears in a wrong position for a short moment.
if you cannot accept these minor defects, I suggest you use onIndicatorPageChange()
only.
v.1.2.0
- Added requirement mentioned in #17.
v1.1.4
- Merged #11.
v1.1.3
- Merged #15 which includes AndroidX support and
resetAutoScroll()
which is equivalent topauseAutoScroll()
and thenresumeAutoScroll()
.
v1.1.2
- Added
ViewGroup container
as an argument toinflateView()
. You should now use it as the parent of when you inflate your view. - Updated gradle version and support library version.
v1.1.0
- Added support for view type. But therefore changed parameters needed in
inflateView()
andbindView()
.
v1.0.5
- Added asepct ratio attribute for
LoopingViewPager
- Rewrote the way of caching Views in
LoopingPagerAdapter
, and therefore separated inflation and data binding - Rewrote the way of implementing ViewPager wrap_content
v1.0.4
- Indicator now works with page skipping as well (By calling
selectCurrentItem()
) - Leviated indicator fluctuating phenomenon when using
onIndicatorProgress()
callback - Added option for smart or non-smart indicators
v1.0.1
- Fixed a bug where getSelectingIndicatorPosition() is returning incorrect value.
- Updated README.md according to PageIndicatorView v1.0.0 update.
Copyright 2017 Sira Lam
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the LoopingViewPager), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.