有没有办法在android中使用viewpager适配器减慢滚动速度?
你知道,我一直在看这段代码。我无法弄清楚我的错误。
try{
Field mScroller = mPager.getClass().getDeclaredField("mScroller");
mScroller.setAccessible(true);
Scroller scroll = new Scroller(cxt);
Field scrollDuration = scroll.getClass().getDeclaredField("mDuration");
scrollDuration.setAccessible(true);
scrollDuration.set(scroll, 1000);
mScroller.set(mPager, scroll);
}catch (Exception e){
Toast.makeText(cxt, "something happened", Toast.LENGTH_LONG).show();
}
它没有改变任何事情但没有异常发生?
答案 0 :(得分:223)
我已经开始使用HighFlyer的代码确实更改了mScroller字段(这是一个很好的开始),但没有帮助延长滚动的持续时间,因为ViewPager在请求滚动时显式地将持续时间传递给mScroller。
扩展ViewPager不起作用,因为重要的方法(smoothScrollTo)无法被覆盖。
我最后通过使用以下代码扩展Scroller来解决这个问题:
public class FixedSpeedScroller extends Scroller {
private int mDuration = 5000;
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);
}
}
并像这样使用它:
try {
Field mScroller;
mScroller = ViewPager.class.getDeclaredField("mScroller");
mScroller.setAccessible(true);
FixedSpeedScroller scroller = new FixedSpeedScroller(mPager.getContext(), sInterpolator);
// scroller.setFixedDuration(5000);
mScroller.set(mPager, scroller);
} catch (NoSuchFieldException e) {
} catch (IllegalArgumentException e) {
} catch (IllegalAccessException e) {
}
我基本上将持续时间硬编码为5秒,并让我的ViewPager使用它。
希望这有帮助。
答案 1 :(得分:59)
我想自己做并且已经实现了解决方案(但是使用反射)。它与已接受的解决方案类似,但使用相同的插值器,仅根据因子更改持续时间。您需要在XML中使用ViewPagerCustomDuration
而不是ViewPager
,然后才能执行此操作:
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 {
Class<?> viewpager = ViewPager.class;
Field scroller = viewpager.getDeclaredField("mScroller");
scroller.setAccessible(true);
Field interpolator = viewpager.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));
}
}
希望这有助于某人!
答案 2 :(得分:40)
我找到了更好的解决方案,基于@df778899's answer和。{ Android ValueAnimator API。没有反射它工作正常,非常灵活。 此外,无需自定义ViewPager并将其放入android.support.v4.view包中。 这是一个例子:
private void animatePagerTransition(final boolean forward) {
ValueAnimator animator = ValueAnimator.ofInt(0, viewPager.getWidth() - ( forward ? viewPager.getPaddingLeft() : viewPager.getPaddingRight() ));
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);
viewPager.beginFakeDrag();
animator.start();
}
答案 3 :(得分:17)
正如您在ViewPager源中see所做的那样,由mScroller对象控制的fling持续时间。 在documantation我们可以阅读:
滚动的持续时间可以在构造函数中传递,并指定滚动动画应该花费的最长时间
因此,如果要控制速度,可以通过反射更改mScroller对象。
你应该写这样的东西:
setContentView(R.layout.main);
mPager = (ViewPager)findViewById(R.id.view_pager);
Field mScroller = ViewPager.class.getDeclaredField("mScroller");
mScroller.setAccessible(true);
mScroller.set(mPager, scroller); // initialize scroller object by yourself
答案 4 :(得分:16)
这不是完美的解决方案,你不能让速度变慢,因为它是int
。但对我而言,它足够慢,我不必使用反射。
注意类所在的包。 smoothScrollTo
具有包可见性。
package android.support.v4.view;
import android.content.Context;
import android.util.AttributeSet;
public class SmoothViewPager extends ViewPager {
private int mVelocity = 1;
public SmoothViewPager(Context context) {
super(context);
}
public SmoothViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
void smoothScrollTo(int x, int y, int velocity) {
//ignore passed velocity, use one defined here
super.smoothScrollTo(x, y, mVelocity);
}
}
答案 5 :(得分:8)
ViewPager上的fakeDrag方法似乎提供了另一种解决方案。
例如,这将从第0项到第1页进行分页:
rootView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
ViewPager pager = (ViewPager) getActivity().findViewById(R.id.pager);
//pager.setCurrentItem(1, true);
pager.beginFakeDrag();
Handler handler = new Handler();
handler.post(new PageTurner(handler, pager));
}
});
private static class PageTurner implements Runnable {
private final Handler handler;
private final ViewPager pager;
private int count = 0;
private PageTurner(Handler handler, ViewPager pager) {
this.handler = handler;
this.pager = pager;
}
@Override
public void run() {
if (pager.isFakeDragging()) {
if (count < 20) {
count++;
pager.fakeDragBy(-count * count);
handler.postDelayed(this, 20);
} else {
pager.endFakeDrag();
}
}
}
}
(count * count
就是为了让拖动速度提高一样)
答案 6 :(得分:3)
我用过
DecelerateInterpolator()
以下是示例:
mViewPager = (ViewPager) findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
Field mScroller = null;
try {
mScroller = ViewPager.class.getDeclaredField("mScroller");
mScroller.setAccessible(true);
Scroller scroller = new Scroller(this, new DecelerateInterpolator());
mScroller.set(mViewPager, scroller);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
答案 7 :(得分:1)
这是一个使用完全不同的方法的答案。有人可能会说这有一种hacky的感觉;但是,它不使用反射,我认为它将始终有效。
我们在ViewPager.smoothScrollTo
:
if (velocity > 0) {
duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
} else {
final float pageWidth = width * mAdapter.getPageWidth(mCurItem);
final float pageDelta = (float) Math.abs(dx) / (pageWidth + mPageMargin);
duration = (int) ((pageDelta + 1) * 100);
}
duration = Math.min(duration, MAX_SETTLE_DURATION);
它根据一些事情计算持续时间。看到我们可以控制的一切? mAdapter.getPageWidth
。让我们在适配器中实现ViewPager.OnPageChangeListener 。我们将检测ViewPager何时滚动,为宽度提供假值。如果我希望持续时间为k*100
,那么我将1.0/(k-1)
返回getPageWidth
。以下是适配器的这部分的Kotlin impl,它将持续时间变为400:
var scrolling = false
override fun getPageWidth(position: Int): Float {
return if (scrolling) 0.333f else 1f
}
// OnPageChangeListener to detect scroll state
override fun onPageScrollStateChanged(state: Int) {
scrolling = state == ViewPager.SCROLL_STATE_SETTLING
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
}
override fun onPageSelected(position: Int) {
}
不要忘记将适配器添加为OnPageChangedListener。
我的特殊情况可以安全地假设用户无法滑动以在页面之间拖动。如果你必须支持拖动后的结算,那么你需要在计算中做更多的事情。
一个缺点是,这取决于ViewPager中的硬编码100
基本持续时间值。如果这种情况发生变化,那么您的持续时间会随着这种方法而改变。
答案 8 :(得分:1)
基于公认的解决方案,我创建了带有视图寻呼机扩展名的kotlin类。请享用! :)
class ViewPageScroller : Scroller {
var fixedDuration = 1500 //time to scroll in milliseconds
constructor(context: Context) : super(context)
constructor(context: Context, interpolator: Interpolator) : super(context, interpolator)
constructor(context: Context, interpolator: Interpolator, flywheel: Boolean) : super(context, interpolator, flywheel)
override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, duration: Int) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, fixedDuration)
}
override fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int) {
// Ignore received duration, use fixed one instead
super.startScroll(startX, startY, dx, dy, fixedDuration)
}
}
fun ViewPager.setViewPageScroller(viewPageScroller: ViewPageScroller) {
try {
val mScroller: Field = ViewPager::class.java.getDeclaredField("mScroller")
mScroller.isAccessible = true
mScroller.set(this, viewPageScroller)
} catch (e: NoSuchFieldException) {
} catch (e: IllegalArgumentException) {
} catch (e: IllegalAccessException) {
}
}
答案 9 :(得分:0)
binding.vpTour.beginFakeDrag();
lastFakeDrag = 0;
ValueAnimator va = ValueAnimator.ofInt(0, binding.vpTour.getWidth());
va.setDuration(1000);
va.addUpdateListener(animation -> {
if (binding.vpTour.isFakeDragging()) {
int animProgress = (Integer) animation.getAnimatedValue();
binding.vpTour.fakeDragBy(lastFakeDrag - animProgress);
lastFakeDrag = animProgress;
}
});
va.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
if (binding.vpTour.isFakeDragging()) {
binding.vpTour.endFakeDrag();
}
}
});
va.start();
我想解决与你们所有人相同的问题,这是我对同一问题的解决方案,但我认为这种解决方案更加灵活,因为你可以根据需要改变持续时间,也可以改变插值。根据需要获得不同的视觉效果值。 我的解决方案从页面“1”滑动到页面“2”,因此只能在增加位置,但可以通过执行“animProgress - lastFakeDrag”而不是“lastFakeDrag - animProgress”轻松更改为减少位置。 我认为这是执行此任务的最灵活的解决方案。