Android 解决 SmartRefreshLayout 刷新完毕后 RecyclerView 会滚动一段距离的问题

一、背景

  1. scwang90/SmartRefreshLayout 官网,当前版本 2.0.3
  2. 官方 demo 下载地址
  3. 遇到的问题:

当 RecyclerView 上方有位置固定的控件存在时,从该控件开始触摸触发下拉刷新,刷新完毕后,RecyclerView 会自动滚动一段距离,造成视觉上的抖动。而且是每触发一次刷新,就会移动一段距离,感觉还是不太爽的。

Android 解决 SmartRefreshLayout 刷新完毕后 RecyclerView 会滚动一段距离的问题

二、复现问题

  1. build.gradle

// 万能适配器
implementation  com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.2 

// 下拉刷新
implementation   com.scwang.smart:refresh-layout-kernel:2.0.3 
// 谷歌刷新头
implementation   com.scwang.smart:refresh-header-material:2.0.3 
// 经典刷新头
implementation   com.scwang.smart:refresh-header-classics:2.0.3 

  1. OfficialPullRefreshActivity.kt

class OfficialPullRefreshActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_official_pull_refresh)

        val list = ArrayList<String>()
        for (i in 0..25) {
            val char =  a  + i
            list.add(char.toString())
        }

        val smartRefreshLayout = findViewById<SmartRefreshLayout>(R.id.refresh_layout)
        smartRefreshLayout.setOnRefreshListener {
            it.finishRefresh(2000)
        }

        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
        recyclerView.adapter = MyAdapter(list).apply {
            setOnItemClickListener { adapter, view, position ->
                ToastUtils.showShort("$position clicked.")
            }
        }
        recyclerView.layoutManager = LinearLayoutManager(this)
    }

    private class MyAdapter(list: MutableList<String>) :
        BaseQuickAdapter<String, BaseViewHolder>(android.R.layout.simple_list_item_1, list) {
        override fun convert(holder: BaseViewHolder, item: String) {
            holder.setText(android.R.id.text1, item)
        }
    }

}

  1. activity_official_pull_refresh.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.scwang.smart.refresh.layout.SmartRefreshLayout
        android:id="@+id/refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.scwang.smart.refresh.header.ClassicsHeader
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <include layout="@layout/content_pull_refresh" />
    </com.scwang.smart.refresh.layout.SmartRefreshLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

  1. content_pull_refresh.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/recycler_view_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:background="#7fff0000"
        android:gravity="center"
        android:text="This is banner"
        android:textSize="40dp" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:listitem="@android:layout/simple_list_item_1" />
</LinearLayout>

  1. 效果
  • 经典刷新头

    Android 解决 SmartRefreshLayout 刷新完毕后 RecyclerView 会滚动一段距离的问题

  • 谷歌刷新头

    Android 解决 SmartRefreshLayout 刷新完毕后 RecyclerView 会滚动一段距离的问题

可以看到,该问题的确 是存在的。

三、解决1:简单解决

每次刷新后主动滚动到顶部位置,否则每下拉刷新一次会向上滚动一点,直到滚动到顶部不能再滚动

val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = MyAdapter(list).apply {
    setOnItemClickListener { adapter, view, position ->
        ToastUtils.showShort("$position clicked.")
    }

    recyclerView.post {
        recyclerView.scrollToPosition(0)
    }
}
recyclerView.layoutManager = LinearLayoutManager(this)

四、解决2:不允许从固定控件的位置触摸时触发刷新

用到了官方提供的 setScrollBoundaryDecider 接口,用于指定一个自定义边界来界定是否触发刷新

smartRefreshLayout.setScrollBoundaryDecider(object : SimpleBoundaryDecider() {
    override fun canRefresh(content: View): Boolean {
        return !recyclerView.canScrollVertically(-1)
    }

    override fun canLoadMore(content: View): Boolean {
        return super.canLoadMore(content)
    }
})

效果:

Android 解决 SmartRefreshLayout 刷新完毕后 RecyclerView 会滚动一段距离的问题

可以看到,如果 RecyclerView 还没有滚动到顶部(第 0 个 item 没有完全显示出来)则不允许从 This is banner 的位置开始触发刷新,只能先把 RecyclerView 完全滚动到顶部后才能触发下拉刷新。

五、解决3:彻底解决

  1. MySmartRefreshLayout.kt

class MySmartRefreshLayout : SmartRefreshLayout {

    constructor (context: Context) : this(context, null)

    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)

    override fun setRefreshContent(content: View): RefreshLayout {
        super.setRefreshContent(content)
        mRefreshContent = MyRefreshContentWrapper(content)

        return this
    }

}

  1. MyRefreshContentWrapper.kt

class MyRefreshContentWrapper(view: View) : RefreshContentWrapper(view) {

    override fun scrollContentWhenFinished(spinner: Int): ValueAnimator.AnimatorUpdateListener? {
        return null
    }

}

  1. MyPullRefreshActivity.kt

class MyPullRefreshActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_my_pull_refresh)

        val list = ArrayList<String>()
        for (i in 0..25) {
            val char =  a  + i
            list.add(char.toString())
        }

        val smartRefreshLayout = findViewById<SmartRefreshLayout>(R.id.refresh_layout)
        smartRefreshLayout.setOnRefreshListener {
            it.finishRefresh(2000)
        }

        layoutInflater.inflate(R.layout.content_pull_refresh, null).also {
            smartRefreshLayout.setRefreshContent(it)
        }

        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
        recyclerView.adapter = MyAdapter(list).apply {
            setOnItemClickListener { adapter, view, position ->
                ToastUtils.showShort("$position clicked.")
            }
        }
        recyclerView.layoutManager = LinearLayoutManager(this)
    }

    private class MyAdapter(list: MutableList<String>) :
        BaseQuickAdapter<String, BaseViewHolder>(android.R.layout.simple_list_item_1, list) {
        override fun convert(holder: BaseViewHolder, item: String) {
            holder.setText(android.R.id.text1, item)
        }
    }

}

  1. activity_my_pull_refresh.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <top.gangshanghua.xiaobo.helloworld.ui.refresh.MySmartRefreshLayout
        android:id="@+id/refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.scwang.smart.refresh.header.ClassicsHeader
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <!-- 注意这里没有 include-->
        <!-- <include layout="@layout/content_pull_refresh" /> -->

    </top.gangshanghua.xiaobo.helloworld.ui.refresh.MySmartRefreshLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

  1. content_pull_refresh.xml

同上

通过 MySmartRefreshLayout#setRefreshContent(View) 接口来传递真正的 content。先调用父类的 super.setRefreshContent(content),父类的该方法里会给 mRefreshContent 赋值,默认是 RefreshContentWrapper 的实例,所以在 MySmartRefreshLayout 中在对 mRefreshContent 进行重新赋值为 MyRefreshContentWrapper 的实例,达到偷梁换柱的效果!

所以会运行到 MyRefreshContentWrapper#scrollContentWhenFinished(int) 来处理结束刷新后 content 的滑动问题,在我们这种情况下是不需要滑动 content 的,所以直接返回 null 即可。如果不返回 null 会在 RefreshContentWrapper#onAnimationUpdate(ValueAnimator) 调用 RecyclerView#scrollBy(int, int) 完成真正的滚动。

Android 解决 SmartRefreshLayout 刷新完毕后 RecyclerView 会滚动一段距离的问题

  1. 效果
  • 经典刷新头

    Android 解决 SmartRefreshLayout 刷新完毕后 RecyclerView 会滚动一段距离的问题

  • 谷歌刷新头

    Android 解决 SmartRefreshLayout 刷新完毕后 RecyclerView 会滚动一段距离的问题

六、回归原生

implementation “androidx.swiperefreshlayout:swiperefreshlayout:1.1.0”

  1. activity_google_pull_refresh.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/swipe_refresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <include layout="@layout/content_pull_refresh" />

    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

  1. content_pull_refresh.xml

同上

  1. GooglePullRefreshActivity.kt

class GooglePullRefreshActivity : AppCompatActivity() {

    private lateinit var mRecyclerView: RecyclerView
    private lateinit var mSwipeRefreshLayout: SwipeRefreshLayout

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_google_pull_refresh)

        mRecyclerView = findViewById(R.id.recycler_view)
        mRecyclerView.layoutManager = LinearLayoutManager(this)

        mSwipeRefreshLayout = findViewById(R.id.swipe_refresh)
        mSwipeRefreshLayout.apply {
            setOnRefreshListener {
                postDelayed(2000L) {
                    isRefreshing = false
                }
            }
        }

        initData()
    }

    private fun initData() {
        val list = ArrayList<String>()
        for (i in 0..25) {
            val char =  a  + i
            list.add(char.toString())
        }

        mRecyclerView.adapter = MyAdapter(list)
    }

    private class MyAdapter(list: MutableList<String>) :
        BaseQuickAdapter<String, BaseViewHolder>(android.R.layout.simple_list_item_1, list) {
        override fun convert(holder: BaseViewHolder, item: String) {
            holder.setText(android.R.id.text1, item)
        }
    }

}

  1. 效果

    Android 解决 SmartRefreshLayout 刷新完毕后 RecyclerView 会滚动一段距离的问题

  2. 说明
    可以看到,原生 GooglePullRefreshActivity 就已经支持将 RecyclerView 包裹在容器里了,如这里用到的 LinearLayout,同时在容器里添加了其它的控件。而且,也支持在 This is banner 的位置触发刷新,并且刷新后不会出现 RecyclerView 还会莫名滚动的问题

  3. 局限
    这样的局限是只能使用默认下下拉刷新头

七、最后

Android 解决 SmartRefreshLayout 刷新完毕后 RecyclerView 会滚动一段距离的问题

八、补充

通过查看 SmartRefreshLayout 源码,发现其提供了 SmartRefreshLayout#setEnableScrollContentWhenRefreshed(boolean) 方法可以用于控制是否在刷新完成后滚动内容。

© 版权声明

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
none
暂无评论...