为了对水平RecyclerView上的项目进行简短的概述,我们希望有一个类似反弹的动画,该动画从某个位置开始,一直到RecyclerView的开始(例如,从项目3到项目0)。
由于某种原因,我尝试的所有Interpolator类(可用插图here)似乎都不允许物品进入RecyclerView之外或在其上反弹。
更具体地说,我尝试了OvershootInterpolator,BounceInterpolator和其他一些类似的方法。我什至尝试了AnticipateOvershootInterpolator。在大多数情况下,它可以进行简单的滚动,而不会产生特殊效果。在AnticipateOvershootInterpolator上,它甚至不会滚动...
下面是我制作的POC的代码,以显示此问题:
MainActivity.kt
class MainActivity : AppCompatActivity() {
val handler = Handler()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val itemSize = resources.getDimensionPixelSize(R.dimen.list_item_size)
val itemsCount = 6
recyclerView.adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val imageView = ImageView(this@MainActivity)
imageView.setImageResource(android.R.drawable.sym_def_app_icon)
imageView.layoutParams = RecyclerView.LayoutParams(itemSize, itemSize)
return object : RecyclerView.ViewHolder(imageView) {}
}
override fun getItemCount(): Int = itemsCount
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
}
}
val itemToGoTo = Math.min(3, itemsCount - 1)
val scrollValue = itemSize * itemToGoTo
recyclerView.post {
recyclerView.scrollBy(scrollValue, 0)
handler.postDelayed({
recyclerView.smoothScrollBy(-scrollValue, 0, BounceInterpolator())
}, 500L)
}
}
}
activity_main.xml
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
android:layout_height="@dimen/list_item_size" android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
渐变文件
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.example.myapplication"
minSdkVersion 15
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.0.0-rc02'
implementation 'androidx.core:core-ktx:1.0.0-rc02'
implementation 'androidx.constraintlayout:constraintlayout:1.1.2'
implementation 'androidx.recyclerview:recyclerview:1.0.0-rc02'
}
这是它如何寻找BounceInterpolator的动画,如您所见,它根本不会反弹:
可用的示例POC项目here
为什么它不能按预期工作,我该如何解决?
RecyclerView是否可以与Interpolator一起滚动使用?
编辑:看来这是个错误,因为我不能在RecyclerView滚动中使用任何“有趣的”内插器,所以我已经对此进行了报道here。
答案 0 :(得分:6)
我将看一下Google的支持动画包。具体是https://developer.android.com/reference/android/support/animation/DynamicAnimation#SCROLL_X
它看起来像:
SpringAnimation(recyclerView, DynamicAnimation.SCROLL_X, 0f)
.setStartVelocity(1000)
.start()
更新:
看起来也不行。我查看了RecyclerView的一些来源,以及反弹插补器不起作用的原因是因为RecyclerView没有正确使用插补器。有一个对computeScrollDuration
的调用,对插值器的调用随后获得了原始动画时间(以秒为单位),而不是该值占动画总时间的百分比。这个值也不是完全可以预测的,我测试了一些值,并看到了100ms-250ms的任何地方。无论如何,从我所看到的来看,您有两个选择(我都对它们进行了测试)
实现您自己的属性并使用春季动画:
class ScrollXProperty : FloatPropertyCompat("scrollX") {
override fun setValue(obj: RecyclerView, value: Float) {
obj.scrollBy(value.roundToInt() - getValue(obj).roundToInt(), 0)
}
override fun getValue(obj: RecyclerView): Float =
obj.computeHorizontalScrollOffset().toFloat()
}
然后在弹跳中,将对smoothScrollBy
的呼叫替换为以下形式:
SpringAnimation(recyclerView, ScrollXProperty())
.setSpring(SpringForce()
.setFinalPosition(0f)
.setStiffness(SpringForce.STIFFNESS_LOW)
.setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY))
.start()
更新:
第二种解决方案是开箱即用的,无需更改RecyclerView,这是我编写并完全测试过的解决方案。
有关插值器的更多信息,smoothScrollBy
与插值器不能很好地配合(可能是一个错误)。使用插值器时,您基本上会将0-1值映射到另一个值,该值是动画的乘数。示例:t = 0,interp(0)= 0表示在动画开始时该值应与动画开始时相同,t = .5,interp(.5)=。25表示该元素将设置动画1方式的/ 4,等等。反弹插值器基本上在某个点返回值> 1,并在大约1处振荡,直到t = 1时最终稳定为1。
解决方案#2正在使用弹簧动画器,但需要更新滚动条。 SCROLL_X不起作用的原因是RecyclerView实际上没有滚动(这是我的错误)。它根据不同的计算方法来计算视图的位置,这就是为什么需要调用computeHorizontalScrollOffset
的原因。 ScrollXProperty
允许您更改RecyclerView的水平滚动,就好像您在ScrollView中指定了scrollX
属性一样,它基本上是一个适配器。 RecyclerViews仅在平滑滚动中不支持滚动到特定的像素偏移,但是SpringAnimation已经为您顺利进行了滚动,因此我们不需要这样做。相反,我们想滚动到一个离散的位置。参见https://android.googlesource.com/platform/frameworks/support/+/247185b98675b09c5e98c87448dd24aef4dffc9d/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java#387
更新:
这是我用来测试https://github.com/yperess/StackOverflow/tree/52148251
的代码更新:
使用插值器也有相同的概念:
class ScrollXProperty : Property<RecyclerView, Int>(Int::class.java, "horozontalOffset") {
override fun get(`object`: RecyclerView): Int =
`object`.computeHorizontalScrollOffset()
override fun set(`object`: RecyclerView, value: Int) {
`object`.scrollBy(value - get(`object`), 0)
}
}
ObjectAnimator.ofInt(recycler_view, ScrollXProperty(), 0).apply {
interpolator = BounceInterpolator()
duration = 500L
}.start()
GitHub上的演示项目已更新
我更新了ScrollXProperty
以进行优化,它似乎可以在Pixel上正常运行,但尚未在较旧的设备上进行测试。
class ScrollXProperty(
private val enableOptimizations: Boolean
) : Property<RecyclerView, Int>(Int::class.java, "horizontalOffset") {
private var lastKnownValue: Int? = null
override fun get(`object`: RecyclerView): Int =
`object`.computeHorizontalScrollOffset().also {
if (enableOptimizations) {
lastKnownValue = it
}
}
override fun set(`object`: RecyclerView, value: Int) {
val currentValue = lastKnownValue?.takeIf { enableOptimizations } ?: get(`object`)
if (enableOptimizations) {
lastKnownValue = value
}
`object`.scrollBy(value - currentValue, 0)
}
}
GitHub项目现在包括带有以下插补器的演示:
<string-array name="interpolators">
<item>AccelerateDecelerate</item>
<item>Accelerate</item>
<item>Anticipate</item>
<item>AnticipateOvershoot</item>
<item>Bounce</item>
<item>Cycle</item>
<item>Decelerate</item>
<item>Linear</item>
<item>Overshoot</item>
</string-array>
答案 1 :(得分:0)
就像我说的那样,我不会诚实地期望发布候选版本(在您的情况下为recyclerview:1.0.0-rc02
)不会出现任何问题,因为它已经在开发中。
androidx
依赖关系,并且它们已经处于开发阶段,并且 不够稳定 供其他开发人员使用。更新来自Google Developers的答案:
我们已将此问题传递给开发团队,并将更新此问题 并提供更多信息。
因此,最好等待新更新。
答案 2 :(得分:0)
您需要以某种方式启用过度滚动弹跳。
一种可能的解决方案是使用 https://github.com/chthai64/overscroll-bouncy-android
将其包含在您的项目中
implementation 'com.chauthai.overscroll:overscroll-bouncy:0.1.1'
然后在您的POV中将RecyclerView
中的activity_main.xml
更改为com.chauthai.overscroll.RecyclerViewBouncy
<?xml version="1.0" encoding="utf-8"?>
<com.chauthai.overscroll.RecyclerViewBouncy
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="@dimen/list_item_size"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
更改后,您会在应用中看到跳动。