I am writing application that uses ViewPager to host Fragments.
When I change fragment programmatically the smooth scroll function does not work. I use ViewPager.setCurrentItem(int item, boolean smoothScroll)` method.
Maybe anyone know a workaround this bug? Maybe with animations?
EDIT:
I am using support package. And the issue is that whether I use ViewPager.setCurrentItem(2, true) or ViewPager.setCurrentItem(2, false) the result is the same. The view switches really fast (not smoothly).
I've fixed this by creating a MyViewPager that overrides the ViewPager.mScroller using reflection.
public class MyViewPager extends ViewPager
{
public MyViewPager( Context context, AttributeSet attrs)
{
super( context, attrs );
setMyScroller();
}
private void setMyScroller()
{
try
{
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
scroller.set(this, new MyScroller(getContext()));
} catch (Exception e)
{
e.printStackTrace();
}
}
public class MyScroller extends Scroller
{
public MyScroller(Context context)
{
super(context, new DecelerateInterpolator());
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration)
{
super.startScroll(startX, startY, dx, dy, 1000 /*1 secs*/);
}
}
}
What worked for me was to call mPager.setCurrentItem after the mPagerAdapter.notifyDataSetChanged(), and not as a posted runnable.
Here is an example (in MonoDroid):
public void UpdateDetialsView(string type)
{
string typeName4 = Java.Lang.Class.FromType(typeof(ParcelRecordComplaintDetailsView)).Name;
if (_fragments.Count > 3)
{
_fragments.RemoveAt(_fragments.Count - 1);
////////////////////////////////////////////////////////////
// Called here it wasn't working for me (Posted or not)
//_viewPager.Post(new Action(() => { _viewPager.SetCurrentItem(3, true); }));
}
_fragments.Add(Fragment.Instantiate(Activity, typeName4));
_pagerAdapter.NotifyDataSetChanged();
////////////////////////////////////////////////////////////
// Called here it works as expected
_viewPager.SetCurrentItem(3, true);
}
where
protected MyAdapter _pagerAdapter;
protected ViewPager _viewPager;
private List<Android.App.Fragment> _fragments;
and
public class MyAdapter : Android.Support.V13.App.FragmentStatePagerAdapter
{
private List<Fragment> _fragments;
public override Java.Lang.Object InstantiateItem(View p0, int p1)
{
return base.InstantiateItem(p0, p1);
}
public MyAdapter(Android.App.FragmentManager fm) : base(fm)
{
}
public MyAdapter(Android.App.FragmentManager fm, List<Android.App.Fragment> fragments) : base(fm)
{
_fragments = fragments;
}
public override int Count
{
//get { return FRAG_PAGES; }
get { return _fragments.Count; }
}
public override Android.App.Fragment GetItem(int p0)
{
return _fragments[p0];
}
public override float GetPageWidth(int p0)
{
//return base.GetPageWidth(p0);
return (float) (0.5f);
}
public override int GetItemPosition(Java.Lang.Object p0)
{
//return base.GetItemPosition(p0);
return PagerAdapter.PositionNone;
}
}
Related
I am using ViewPager with FragmentStatePageAdapter to show a number of views where user can go through. With current implementation only one page is changed at any single swipe. Instead I want to flip through multiple views based on user swipe. If swipe is long/fast i need to flip through multiple views and vice versa.
here is my current code
public class MyAdapter extends FragmentStatePagerAdapter
{
public MyAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public int getCount() {
return 10;
}
#Override
public Fragment getItem(int position) {
return ItemFragment.init(position);
}
}
PageContainer class
public class PagerContainer extends FrameLayout implements ViewPager.OnPageChangeListener {
private ViewPager mPager;
boolean mNeedsRedraw = false;
public PagerContainer(Context context) {
super(context);
init();
}
public PagerContainer(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public PagerContainer(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
//Disable clipping of children so non-selected pages are visible
setClipChildren(false);
//Child clipping doesn't work with hardware acceleration in Android 3.x/4.x
//You need to set this value here if using hardware acceleration in an
// application targeted at these releases.
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
#Override
protected void onFinishInflate() {
try {
mPager = (ViewPager) getChildAt(0);
mPager.setOnPageChangeListener(this);
} catch (Exception e) {
throw new IllegalStateException("The root child of PagerContainer must be a ViewPager");
}
}
public ViewPager getViewPager() {
return mPager;
}
private Point mCenter = new Point();
private Point mInitialTouch = new Point();
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
mCenter.x = w / 2;
mCenter.y = h / 2;
}
#Override
public boolean onTouchEvent(MotionEvent ev) {
//We capture any touches not already handled by the ViewPager
// to implement scrolling from a touch outside the pager bounds.
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mInitialTouch.x = (int)ev.getX();
mInitialTouch.y = (int)ev.getY();
default:
ev.offsetLocation(mCenter.x - mInitialTouch.x, mCenter.y - mInitialTouch.y);
break;
}
return mPager.dispatchTouchEvent(ev);
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
//Force the container to redraw on scrolling.
//Without this the outer pages render initially and then stay static
if (mNeedsRedraw) invalidate();
}
#Override
public void onPageSelected(int position) { }
#Override
public void onPageScrollStateChanged(int state) {
mNeedsRedraw = (state != ViewPager.SCROLL_STATE_IDLE);
}
}
How can I do this in android?
Thanks
You can change the view by calling:
ViewPager.setCurrentItem(int index)
ViewPager.setCurrentItem(int index, bool animation)
You must handle the sliding commands yourself, but on a certain slide you cloud call setCurrentItem of the ViewPager. The setCurrentItem method also allows you to enable or disable animations.
I have a ViewPager that I want to rotate automatically every 5 seconds, whilst also allowing the user to swipe too. However, when I set the automatic change, the transition animation between pages happens really quickly, and I want this to be slower.
I've seen the answers for this question here:
Slowing speed of Viewpager controller in android
...but they all use reflection. Does anyone know of any way of slowing down the automatic speed of a ViewPager without using reflection?
I thought of using a PageTransformer, but not sure if that would work, and also it would probably affect the speed if the user swiped as well as the automatic one? Unless I can somehow detect the difference, and then do one or another PageTransformation?
Use this Custom View
public class ViewPagerCustomDuration extends ViewPager {
private FixedSpeedScroller mScroller = null;
public ViewPagerCustomDuration(Context context) {
super(context);
init();
}
public ViewPagerCustomDuration(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/*
* Override the Scroller instance with our own class so we can change the
* duration
*/
private void init() {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
mScroller = new FixedSpeedScroller(getContext(),
new DecelerateInterpolator());
scroller.set(this, mScroller);
} catch (Exception ignored) {
}
}
/*
* Set the factor by which the duration will change
*/
public void setScrollDuration(int duration) {
mScroller.setScrollDuration(duration);
}
private class FixedSpeedScroller extends Scroller {
private int mDuration = 500;
public FixedSpeedScroller(Context context) {
super(context);
}
public FixedSpeedScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
public FixedSpeedScroller(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
public void setScrollDuration(int duration) {
mDuration = duration;
}
}
}
You create custom view pager
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.widget.Scroller;
import java.lang.reflect.Field;
public class CustomViewPager extends ViewPager {
private FixedSpeedScroller mScroller = null;
private boolean enabled;
public CustomViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
this.enabled = true;
init();
}
#Override
public boolean onTouchEvent(MotionEvent event) {
if (this.enabled) {
return super.onTouchEvent(event);
}
init();
return false;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (this.enabled) {
return super.onInterceptTouchEvent(event);
}
return false;
}
public void setPagingEnabled(boolean enabled) {
this.enabled = enabled;
}
/*
* Override the Scroller instance with our own class so we can change the
* duration
*/
private void init() {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
mScroller = new FixedSpeedScroller(getContext(),
new DecelerateInterpolator());
scroller.set(this, mScroller);
} catch (Exception ignored) {
}
}
/*
* Set the factor by which the duration will change
*/
public void setScrollDuration(int duration) {
mScroller.setScrollDuration(duration);
}
private class FixedSpeedScroller extends Scroller {
private int mDuration = 500;
public FixedSpeedScroller(Context context) {
super(context);
}
public FixedSpeedScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
public FixedSpeedScroller(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
public void setScrollDuration(int duration) {
mDuration = duration;
}
}
}
you can set duration of view pager according to you
use schedule timer option for auto rotate the pages.
int i = 0;
static Timer timer = new Timer();
//Scroller scroll = new Scroller(this);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_circles);
mAdapter = new TestFragmentAdapter(getSupportFragmentManager());
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mIndicator = (CirclePageIndicator) findViewById(R.id.indicator);
mIndicator.setViewPager(mPager);
mPager.setCurrentItem(i);
SwitchPage(3);
}
public void SwitchPage(int seconds)
{
if(timer != null)
{
timer.cancel();
}
timer = new Timer(); // At this line a new Thread will be created
timer.schedule(new SwitchPageTask(),
2000, seconds * 2000);
// delay in milliseconds
}
class SwitchPageTask extends TimerTask
{
#Override
public void run() {
runOnUiThread(new Runnable() {
public void run() {
if(i < mAdapter.getCount())
{
i++;
mPager.setCurrentItem(i, true);
}
else
{
i=0;
mPager.setCurrentItem(i, true);
}
}
});
}
}
just cancel the timer like.. timer.cancel().. or
cancel timer from another activity or fragment activity..
textView1_page3.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (SampleCirclesDefault.timer != null) {
SampleCirclesDefault.timer.cancel();
Toast.makeText(getActivity(), "timer
cancled", 1).show();
}
}
});
I have the following views: a ViewPager that show 3 Fragments. The user can scroll horizontaly to see the others Fragment in the ViewPager.
I need to create an animation, when the Activity is created and the ViewPager is populated, that show the Fragments entering from the right side of the screen.
Is that even possible? Thanks.
XML:
<android.support.v4.view.ViewPager
android:id="#+id/devices"
android:layout_width="wrap_content"
android:layout_height="150dp" />
Activity:
MyFragmentStatePagerAdapter mAdapter = new MyFragmentStatePagerAdapter( this.getSupportFragmentManager(), fragmentArrayList );
mAdapter.setPageWidth(0.33333333f);
ViewPager devices = (ViewPager) view.findViewById(R.id.devices);
devices.setAdapter(sensorAdapter);
Adapter:
public class MyFragmentStatePagerAdapter extends FragmentStatePagerAdapter {
ArrayList<Fragment> fragmentsList;
private float pageWidth = 1;
public void setPageWidth(float pageWidth) { this.pageWidth = pageWidth; }
public MyFragmentStatePagerAdapter(FragmentManager fm, ArrayList<Fragment> fragmentsList) {
super(fm);
this.fragmentsList = fragmentsList;
}
#Override
public int getCount() {
return fragmentsList.size();
}
#Override
public Fragment getItem(int position) {
if (position<fragmentsList.size() & fragmentsList.get(position)!=null) {
return fragmentsList.get(position);
}
return null;
}
#Override
public float getPageWidth(int position) {
return(pageWidth);
}
}
OK, I found the solution. I just had to extend ViewPager and do the animation in onDraw.
This code is working as I needed:
public class ViewPagerCustom extends ViewPager {
private Context mContext;
private boolean startAnimationDone = false;
public ViewPagerCustom(Context context) {
super(context);
mContext = context;
init();
}
public ViewPagerCustom(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
init();
}
DisplayMetrics metrics;
TranslateAnimation anim;
private void init() {
metrics = mContext.getResources().getDisplayMetrics();
anim = new TranslateAnimation(metrics.widthPixels+200, 0, 0, 0);
anim.setInterpolator(new DecelerateInterpolator());
anim.setDuration(500);
}
private Canvas enterAnimation(Canvas c) {
startAnimationDone = true;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
child.startAnimation(anim);
}
return c;
}
#Override
protected void onDraw(Canvas c) {
if (startAnimationDone==false) {
c = enterAnimation(c);
}
super.onDraw(c);
}
}
Here is a link which might help you:
Zoom-out page transformer
It's similar to the accepted solution but uses the same interpolator and only changes the duration based on a factor. You need to use a ViewPagerCustomDuration in your XML instead of ViewPager, and then you can do this:
ViewPagerCustomDuration vp = (ViewPagerCustomDuration) findViewById(R.id.myPager);
vp.setScrollDurationFactor(2); // make the animation twice as slow
Can a View Pager be made to auto slide or autopage. I have my viewpager set up to use the adapter like the below and it works fine:-
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.gridslide);
ImagePagerAdapter mAdapter = new ImagePagerAdapter(
getSupportFragmentManager(),4);
ViewPager mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
}
And the adapter is like below:-
public static class ImagePagerAdapter extends FragmentStatePagerAdapter {
private final int mSize;
public ImagePagerAdapter(FragmentManager fm, int size) {
super(fm);
mSize = size;
}
#Override
public int getCount() {
return mSize;
}
#Override
public Fragment getItem(int position) {
Log.v(TAG,"position="+position);
return TheFragment.newInstance(position);
}}
However I would want to know how to make these fragments autoslide in a viewpager.
Setting smoothScroll = true in setCurrentItem(int item, boolean smoothScroll) does not always have the smooth scroll effect. Suppose if you have less than 5 pages in you viewpager, you will hardly notice the smooth scroll.
In this scenario, the hard way to do it is to put it in a for loop
//This will scroll page-by-page so that you can view scroll happening
for (int i = 0; i < mAdapter.getCount()-1; i++)
mPager.setCurrentItem(i, true);
If some one needs more slower scroll, they can use postDelayed() like this...
static int i=0;
private final Handler handler = new Handler();
somefunction()
{
handle.post(ViewPagerVisibleScroll);
}
Runnable ViewPagerVisibleScroll= new Runnable() {
#Override
public void run() {
if(i <= mAdapter.getCount()-1)
{
mPager.setCurrentItem(i, true);
handle.postDelayed(TopChartAnimation, 100);
i++;
}
}
};
Sleep is always NOT recommended : If some one needs more slower scroll, they may use a sleep in this for loop...
#Override
public void onClick(View v) {
Runnable runnable = new Runnable() {
#Override
public void run() {
for (int i = 0; i < mAdapter.getCount()-1; i++) {
final int value = i;
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.post(new Runnable() {
#Override
public void run() {
mPager.setCurrentItem(value, true);
}
});
}
}
};
new Thread(runnable).start();
}
Probably, You should take a look at the following APIs ViewPager.beginFakeDrag(), ViewPager. fakeDragBy(float offset) and ViewPager.endFakeDrag() if You want drag simulation. Also, setCurrentItem() APIs provide ability to set current page and do it smoothly.
Simplest way I could suggest to make slide automatically is to setup Handler with simple Runnable which would call pager methods for setting item in the activity and just do postDelayed() for it. And don't forget to call removeCallbacks() for it when user interaction or e.g. activity pause.
First Create Slider class extend with TimerTask
public class SliderTimer extends TimerTask {
private ViewPager viewPager;
private int size;
private Activity activity;
public SliderTimer(ViewPager viewPager, int size, Activity activity) {
this.viewPager = viewPager;
this.size = size;
this.activity = activity;
}
#Override
public void run() {
activity.runOnUiThread(() -> {
if (viewPager.getCurrentItem() < size - 1) {
viewPager.setCurrentItem(viewPager.getCurrentItem() + 1, true);
} else {
viewPager.setCurrentItem(0, true);
}
});
}
}
Next Create SpeedSlowScroller for slow scrolling
public class SpeedSlowScroller extends Scroller {
private int mDuration = 2500;
public SpeedSlowScroller(Context context) {
super(context);
}
public SpeedSlowScroller(Context context, Interpolator interpolator) {
super(context, interpolator);
}
public SpeedSlowScroller(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
#Override
public void startScroll(int startX, int startY, int dx, int dy) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, mDuration);
}
}
Finally add SpeedSlowScroller into ViewPager, and SliderTimer for auto scroll
try {
Field mScroller = ViewPager.class.getDeclaredField("mScroller");
mScroller.setAccessible(true);
SpeedSlowScroller scroller = new SpeedSlowScroller(_context);
mScroller.set(your_viewpager, scroller);
Timer timer = new Timer();
timer.scheduleAtFixedRate(new SliderTimer(your_viewpager, models.size(), activity), 4000, 6000);
} catch (Exception ignored) {
}
short answer 2020
After you set your viewPager adapter
private Runnable runnable = null;
public void function DisplaySlider(){
sliderItemAdapter = new SliderItemAdapter(getActivity(), mSliderList);//push the data to the adapter
mViewPager.setAdapter(sliderItemAdapter); //set the adapter to the view pager
startAutoSlider(sliderItem.getCount());
}
private void startAutoSlider(final int count) {
runnable = new Runnable() {
#Override
public void run() {
int pos = mViewPager.getCurrentItem();
pos = pos + 1;
if (pos >= count) pos = 0;
mViewPager.setCurrentItem(pos);
handler.postDelayed(runnable, 3000);
}
};
handler.postDelayed(runnable, 3000);
}
And finally don`t forget to free the memory from the callbacks
#Override
public void onDestroy() {
if (runnable != null) handler.removeCallbacks(runnable);
super.onDestroy();
}
I'm changing slide with the following code:
viewPager.setCurrentItem(index++, true);
But it changes too fast. Is there a way to set manually the animation speed?
I've wanted to do myself and have achieved a solution (using reflection, however). I haven't tested it yet but it should work or need minimal modification. Tested on Galaxy Nexus JB 4.2.1. You need to use a ViewPagerCustomDuration in your XML instead of ViewPager, and then you can do this:
ViewPagerCustomDuration vp = (ViewPagerCustomDuration) findViewById(R.id.myPager);
vp.setScrollDurationFactor(2); // make the animation twice as slow
ViewPagerCustomDuration.java:
import android.content.Context;
import android.support.v4.view.ViewPager;
import android.util.AttributeSet;
import android.view.animation.Interpolator;
import java.lang.reflect.Field;
public class ViewPagerCustomDuration extends ViewPager {
public ViewPagerCustomDuration(Context context) {
super(context);
postInitViewPager();
}
public ViewPagerCustomDuration(Context context, AttributeSet attrs) {
super(context, attrs);
postInitViewPager();
}
private ScrollerCustomDuration mScroller = null;
/**
* Override the Scroller instance with our own class so we can change the
* duration
*/
private void postInitViewPager() {
try {
Field scroller = ViewPager.class.getDeclaredField("mScroller");
scroller.setAccessible(true);
Field interpolator = ViewPager.class.getDeclaredField("sInterpolator");
interpolator.setAccessible(true);
mScroller = new ScrollerCustomDuration(getContext(),
(Interpolator) interpolator.get(null));
scroller.set(this, mScroller);
} catch (Exception e) {
}
}
/**
* Set the factor by which the duration will change
*/
public void setScrollDurationFactor(double scrollFactor) {
mScroller.setScrollDurationFactor(scrollFactor);
}
}
ScrollerCustomDuration.java:
import android.annotation.SuppressLint;
import android.content.Context;
import android.view.animation.Interpolator;
import android.widget.Scroller;
public class ScrollerCustomDuration extends Scroller {
private double mScrollFactor = 1;
public ScrollerCustomDuration(Context context) {
super(context);
}
public ScrollerCustomDuration(Context context, Interpolator interpolator) {
super(context, interpolator);
}
#SuppressLint("NewApi")
public ScrollerCustomDuration(Context context, Interpolator interpolator, boolean flywheel) {
super(context, interpolator, flywheel);
}
/**
* Set the factor by which the duration will change
*/
public void setScrollDurationFactor(double scrollFactor) {
mScrollFactor = scrollFactor;
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, (int) (duration * mScrollFactor));
}
}
I have found better solution, based on #df778899's answer and the
Android ValueAnimator API. It works fine without reflection and is very flexible.
Also there is no need for making custom ViewPager and putting it into android.support.v4.view package.
Here is an example:
private void animatePagerTransition(final boolean forward) {
ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth());
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationCancel(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.setInterpolator(new AccelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
private int oldDragPosition = 0;
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int dragPosition = (Integer) animation.getAnimatedValue();
int dragOffset = dragPosition - oldDragPosition;
oldDragPosition = dragPosition;
viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
}
});
animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS);
if (viewPager.beginFakeDrag()) {
animator.start();
}
}
UPDATE:
Just checked if this solution can be used to swipe several pages at once (for example if first page should be showed after the last one). This is slightly modified code to handle specified page count:
private int oldDragPosition = 0;
private void animatePagerTransition(final boolean forward, int pageCount) {
// if previous animation have not finished we can get exception
if (pagerAnimation != null) {
pagerAnimation.cancel();
}
pagerAnimation = getPagerTransitionAnimation(forward, pageCount);
if (viewPager.beginFakeDrag()) { // checking that started drag correctly
pagerAnimation.start();
}
}
private Animator getPagerTransitionAnimation(final boolean forward, int pageCount) {
ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth() - 1);
animator.addListener(new Animator.AnimatorListener() {
#Override
public void onAnimationStart(Animator animation) {
}
#Override
public void onAnimationEnd(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationCancel(Animator animation) {
viewPager.endFakeDrag();
}
#Override
public void onAnimationRepeat(Animator animation) {
viewPager.endFakeDrag();
oldDragPosition = 0;
viewPager.beginFakeDrag();
}
});
animator.setInterpolator(new AccelerateInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
int dragPosition = (Integer) animation.getAnimatedValue();
int dragOffset = dragPosition - oldDragPosition;
oldDragPosition = dragPosition;
viewPager.fakeDragBy(dragOffset * (forward ? -1 : 1));
}
});
animator.setDuration(AppConstants.PAGER_TRANSITION_DURATION_MS / pageCount); // remove divider if you want to make each transition have the same speed as single page transition
animator.setRepeatCount(pageCount);
return animator;
}
public class PresentationViewPager extends ViewPager {
public static final int DEFAULT_SCROLL_DURATION = 250;
public static final int PRESENTATION_MODE_SCROLL_DURATION = 1000;
public PresentationViewPager (Context context) {
super(context);
}
public PresentationViewPager (Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setDurationScroll(int millis) {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
scroller.set(this, new OwnScroller(getContext(), millis));
} catch (Exception e) {
e.printStackTrace();
}
}
public class OwnScroller extends Scroller {
private int durationScrollMillis = 1;
public OwnScroller(Context context, int durationScroll) {
super(context, new DecelerateInterpolator());
this.durationScrollMillis = durationScroll;
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, durationScrollMillis);
}
}
}
Better solution is to simply access the private fields by creating the class in the support package. EDIT This is bound to the MAX_SETTLE_DURATION of 600ms, set by the ViewPagerclass.
package android.support.v4.view;
import android.content.Context;
import android.util.AttributeSet;
public class SlowViewPager extends ViewPager {
// The speed of the scroll used by setCurrentItem()
private static final int VELOCITY = 200;
public SlowViewPager(Context context) {
super(context);
}
public SlowViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
void setCurrentItemInternal(int item, boolean smoothScroll, boolean always) {
setCurrentItemInternal(item, smoothScroll, always, VELOCITY);
}
}
You can, of course, then add a custom attribute so this can be set via XML.
Here is my code used in Librera Reader
public class MyViewPager extends ViewPager {
public MyViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
initMyScroller();
}
private void initMyScroller() {
try {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
scroller.set(this, new MyScroller(getContext())); // my liner scroller
Field mFlingDistance = viewpager.getDeclaredField("mFlingDistance");
mFlingDistance.setAccessible(true);
mFlingDistance.set(this, Dips.DP_10);//10 dip
Field mMinimumVelocity = viewpager.getDeclaredField("mMinimumVelocity");
mMinimumVelocity.setAccessible(true);
mMinimumVelocity.set(this, 0); //0 velocity
} catch (Exception e) {
LOG.e(e);
}
}
public class MyScroller extends Scroller {
public MyScroller(Context context) {
super(context, new LinearInterpolator()); // my LinearInterpolator
}
#Override
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
super.startScroll(startX, startY, dx, dy, 175);//175 duration
}
}
}
I used Cicero Moura's version to make a Kotlin class that still works perfectly as of Android 10.
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.animation.DecelerateInterpolator
import android.widget.Scroller
import androidx.viewpager.widget.ViewPager
class CustomViewPager(context: Context, attrs: AttributeSet) :
ViewPager(context, attrs) {
private companion object {
const val DEFAULT_SPEED = 1000
}
init {
setScrollerSpeed(DEFAULT_SPEED)
}
var scrollDuration = DEFAULT_SPEED
set(millis) {
setScrollerSpeed(millis)
}
private fun setScrollerSpeed(millis: Int) {
try {
ViewPager::class.java.getDeclaredField("mScroller")
.apply {
isAccessible = true
set(this#CustomViewPager, OwnScroller(millis))
}
} catch (e: Exception) {
e.printStackTrace()
}
}
inner class OwnScroller(private val durationScrollMillis: Int) : Scroller(context, AccelerateDecelerateInterpolator()) {
override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) {
super.startScroll(startX, startY, dx, dy, durationScrollMillis)
}
}
}
Initializing from the activity class:
viewPager.apply {
scrollDuration = 2000
adapter = pagerAdapter
}
After wasting my whole day I found a solution set offscreenPageLimit to total no. of the page.
In order to keep a constant length ViewPager scrolls smooth, setOffScreenLimit(page.length) will keep all the views in memory. However, this poses a problem for any animations that involves calling View.requestLayout function (e.g. any animation that involves making changes to the margin or bounds). It makes them really slow (as per Romain Guy) because the all of the views that's in memory will be invalidated as well. So I tried a few different ways to make things smooth but overriding requestLayout and other invalidate methods will cause many other problems.
A good compromise is to dynamically modify the off screen limit so that most of the scrolls between pages will be very smooth while making sure that all of the in page animations smooth by removing the views when the user. This works really well when you only have 1 or 2 views that will have to make other views off memory.
***Use this when no any solution works because by setting offeset limit u will load all the fragments at the same time