我知道有办法改变
ViewPager
编程幻灯片的动画持续时间(这里)。
但它不起作用
ViewPager2
我试过这个:
try {
final Field scrollerField = ViewPager2.class.getDeclaredField("mScroller");
scrollerField.setAccessible(true);
final ResizeViewPagerScroller scroller = new ResizeViewPagerScroller(getContext());
scrollerField.set(mViewPager, scroller);
} catch (Exception e) {
e.printStackTrace();
}
IDE 给我关于“mScroller”的警告:
无法解析字段“mScroller”
如果我们运行此代码,那将不起作用并给我们以下错误:
No field mScroller in class Landroidx/viewpager2/widget/ViewPager2; (declaration of 'androidx.viewpager2.widget.ViewPager2' appears in /data/app/{packagename}-RWJhF9Gydojax8zFyFwFXg==/base.apk)
那么我们如何实现这个功能呢?
基于 此问题票证 Android 团队不打算支持 ViewPager2 的此类行为,票证中的建议是使用
ViewPager2.fakeDragBy()
。此方法的缺点是您必须提供以像素为单位的页面宽度,但如果您的页面宽度与 ViewPager 的宽度相同,则可以使用该值。
这是示例实现
fun ViewPager2.setCurrentItem(
item: Int,
duration: Long,
interpolator: TimeInterpolator = AccelerateDecelerateInterpolator(),
pagePxWidth: Int = width // Default value taken from getWidth() from ViewPager2 view
) {
val pxToDrag: Int = pagePxWidth * (item - currentItem)
val animator = ValueAnimator.ofInt(0, pxToDrag)
var previousValue = 0
animator.addUpdateListener { valueAnimator ->
val currentValue = valueAnimator.animatedValue as Int
val currentPxToDrag = (currentValue - previousValue).toFloat()
fakeDragBy(-currentPxToDrag)
previousValue = currentValue
}
animator.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator?) { beginFakeDrag() }
override fun onAnimationEnd(animation: Animator?) { endFakeDrag() }
override fun onAnimationCancel(animation: Animator?) { /* Ignored */ }
override fun onAnimationRepeat(animation: Animator?) { /* Ignored */ }
})
animator.interpolator = interpolator
animator.duration = duration
animator.start()
}
为了支持
RTL
,您必须翻转提供给 ViewPager2.fakeDragBy()
的值,因此在使用 fakeDragBy(-currentPxToDrag)
时,使用 fakeDragBy(currentPxToDrag)
而不是 RTL
。
根据官方文档,使用此功能时需要记住一些事情:
负值向前滚动,正值向后滚动(用 RTL 翻转)
在致电
fakeDragBy()
之前使用 beginFakeDrag()
以及在完成后 endFakeDrag()
onPageScrollStateChanged
中的 ViewPager2.OnPageChangeCallback
一起使用,您可以通过 isFakeDragging()
方法区分编程拖动和用户拖动ViewPager2 团队让改变滚动速度变得非常困难。如果您查看 setCurrentItemInternal 方法,它们会实例化自己的私有 ScrollToPosition(..) 对象。与状态管理代码一起,所以这将是您必须以某种方式覆盖的方法。
作为这里的解决方案:https://issuetracker.google.com/issues/122656759,他们说使用
(ViewPager2).fakeDragBy()
,这是超级丑陋的。
不是最好的,只需要等待他们给我们一个API来设置持续时间或复制他们的ViewPager2代码并直接修改他们的LinearLayoutImpl类。
M Tomczynski 给出的解决方案(参考:https://stackoverflow.com/a/59235979/7608625)是一个很好的解决方案。但由于 fakeDrag,当与内置 setCurrentItem() 一起使用时,我面临应用程序随机崩溃,即使我确保在使用 setCurrentItem() 之前调用 endDrag() 。因此,我修改了解决方案以使用 viewpager 的嵌入式回收器视图的scrollBy() 函数,该函数运行良好,没有任何问题。
fun ViewPager2.setCurrentItem(
index: Int,
duration: Long,
interpolator: TimeInterpolator = PathInterpolator(0.8f, 0f, 0.35f, 1f),
pageWidth: Int = width - paddingLeft - paddingRight,
) {
val pxToDrag: Int = pageWidth * (index - currentItem)
val animator = ValueAnimator.ofInt(0, pxToDrag)
var previousValue = 0
val scrollView = (getChildAt(0) as? RecyclerView)
animator.addUpdateListener { valueAnimator ->
val currentValue = valueAnimator.animatedValue as Int
val currentPxToDrag = (currentValue - previousValue).toFloat()
scrollView?.scrollBy(currentPxToDrag.toInt(), 0)
previousValue = currentValue
}
animator.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) { }
override fun onAnimationEnd(animation: Animator) {
// Fallback to fix minor offset inconsistency while scrolling
setCurrentItem(index, true)
post { requestTransform() } // To make sure custom transforms are applied
}
override fun onAnimationCancel(animation: Animator) { /* Ignored */ }
override fun onAnimationRepeat(animation: Animator) { /* Ignored */ }
})
animator.interpolator = interpolator
animator.duration = duration
animator.start()
}
当您希望 ViewPager2 以您的速度、方向和页数滚动时,在单击某个按钮时,请使用您的参数调用此函数。如果你想要从右到左,方向可以是 leftToRight = true ,或者如果你想要从右到左,则为 false ,持续时间以毫秒为单位, numberOfPages 应该为 1 ,除非你想一路返回,此时它应该是你的该 viewPager 的页数:
fun fakeDrag(viewPager: ViewPager2, leftToRight: Boolean, duration: Long, numberOfPages: Int) {
val pxToDrag: Int = viewPager.width
val animator = ValueAnimator.ofInt(0, pxToDrag)
var previousValue = 0
animator.addUpdateListener { valueAnimator ->
val currentValue = valueAnimator.animatedValue as Int
var currentPxToDrag: Float = (currentValue - previousValue).toFloat() * numberOfPages
when {
leftToRight -> {
currentPxToDrag *= -1
}
}
viewPager.fakeDragBy(currentPxToDrag)
previousValue = currentValue
}
animator.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator?) { viewPager.beginFakeDrag() }
override fun onAnimationEnd(animation: Animator?) { viewPager.endFakeDrag() }
override fun onAnimationCancel(animation: Animator?) { /* Ignored */ }
override fun onAnimationRepeat(animation: Animator?) { /* Ignored */ }
})
animator.interpolator = AccelerateDecelerateInterpolator()
animator.duration = duration
animator.start()
}
通过扩展PagerSnapHelper并重写createSnapScroller函数来制作CustomPagerSnapHelper
下面是相同的代码。 注意:在我的例子中,我只是将时间乘以 2 倍,这会导致动画速度变慢。根据您的需要更改时间变量。我建议使用多个来进一步减慢动画速度,而不是硬编码值
public class CustomPagerSnapHelper extends PagerSnapHelper {
RecyclerView mRecyclerView;
static final float MILLISECONDS_PER_INCH = 100f;
private static final int MAX_SCROLL_ON_FLING_DURATION = 100; // ms
CustomPagerSnapHelper(RecyclerView mRecyclerView) {
this.mRecyclerView = mRecyclerView;
}
@Override
protected LinearSmoothScroller createSnapScroller(RecyclerView.LayoutManager layoutManager) {
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
return null;
}
return new LinearSmoothScroller(mRecyclerView.getContext()) {
@Override
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(),
targetView);
final int dx = snapDistances[0];
final int dy = snapDistances[1];
final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
if (time > 0) {
action.update(dx, dy, time*2/* time represent the duration of scroll animation */, mDecelerateInterpolator);
}
}
@Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
@Override
protected int calculateTimeForScrolling(int dx) {
return Math.min(MAX_SCROLL_ON_FLING_DURATION, super.calculateTimeForScrolling(dx));
}
};
}
}
在将适配器注入 viewpager 之前,请使用以下代码..
viewPager = findViewById(R.id.viewPagerMain);
try {
Field mScroller = ViewPager2.class.getDeclaredField("mRecyclerView");
mScroller.setAccessible(true);
RecyclerView recyclerView = (RecyclerView) mScroller.get(viewPager);
Field pagerSnap = ViewPager2.class.getDeclaredField("mPagerSnapHelper");
pagerSnap.setAccessible(true);
CustomPagerSnapHelper customPagerSnapHelper = new CustomPagerSnapHelper(recyclerView);
pagerSnap.set(viewPager,customPagerSnapHelper);
recyclerView.setOnFlingListener(null);
customPagerSnapHelper.attachToRecyclerView(recyclerView);
} catch (Exception e) {
e.printStackTrace();
}
viewPager.setAdapter(new RecyclerViewAdapter(this, images));