Android进阶之路 - RecyclerView停止滑动后Item自动居中(SnapHelper辅助类)

之前一直没注意 SnapHelper 辅助类的功能,去年的时候看到项目中仅通过俩行代码设置 RecyclerView 后就提升了用户体验,觉得还是很有必要了解一下,尝试过后才发现其 PagerSnapHelperLinearSnapHelper 子类可以作用于不同场景,且听吾言

RecyclerView基础

RecyclerView扩展

RecyclerView相关功能

你在开发项目中遇到过这样的场景吗?

HintRecyclerView 为水平滑动 && 子ItemView 宽度非 match_parent(支持同屏展示多个ItemView

我们之所以使用 SnapHelper 就是为了让 RecyclerView停止滑动后实现 position 自动居中的效果

  • 用户滑动列表时产生类似 ViewPager 效果,停止滑动后ItemView 自动居中(一般正常速度滑动只滑动一条数据,但是当滑动速度加快(比较费力时),可能会滑动多条数据
  • 用户正常速度滑动列表时可更轻易的滑动多条数据,停止滑动后子ItemView自动居中

Look效果:如果以下效果不能完全满足,也可以自定义SnapHelper,然后参考其子类实现增添部分你需要的业务功能,例如修改滑动速度等

请添加图片描述

Tip:核心方法仅有俩行,如急于开发,亦可直接使用或直接看实践检验,等有时间再来一同了解

创建对应的 SnapHelper 后通过 attachToRecyclerView 关联 RecyclerView 即可

  • PagerSnapHelper
   val pagerSnapHelper = PagerSnapHelper()
   pagerSnapHelper.attachToRecyclerView(mRvPager)
  • LinearSnapHelper
   val linearSnapHelper = LinearSnapHelper()
   linearSnapHelper .attachToRecyclerView(mRvLinear)

基础了解

SnapHelper 自身为抽象类,同时继承了RecyclerView.OnFlingListener,内部实现了一些通用基类方法,拥有俩个实现子类,通过重写其中部分方法,从而达到对应的需求效果

  • PagerSnapHelper:类似ViewPager滑动效果,仅支持单条滑动!在 ViewPager控件中也可以看到PagerSnapHelper的身影(不过有时候在滑动速度较快,滑动幅度较大时可能会滑动多个卡片)
  • LinearSnapHelp:水平快速滑动列表,体验丝滑,当滑动停止后,ItemView 自动居中

在这里插入图片描述

OnFlingListener 仅拥有一个抽象方法

在这里插入图片描述

因为我只是通过源码方法命名 + 参考方法注释 简单理解,可能并不是很详细,有兴趣的可以前往早期一位前辈写的 让你明明白白的使用RecyclerView——SnapHelper详解

通过查看 SnapHelper 内部方法,简单分析一下方法作用范围(仅做部分解释,并不完全)

  • 支持 绑定RecyclerView
  • calculateDistanceToFinalSnap 测量移动距离
  • findSnapView 支持 定位移动的View
  • findTargetSnapPosition 支持定位移动后的数据(视图)角标
  • FlingListenerScrollListener 滑动监听&滑动速度监听

在这里插入图片描述

PagerSnapHelperLinearSnapHelper 除基类方法外,支持获取居中View、布局方向等

PagerSnapHelper 源码方法

在这里插入图片描述

LinearSnapHelper 源码方法

在这里插入图片描述

如果要自定义 SnapHelper 的话,需要重新以下三个抽象方法

package com.example.recyclerviewsnaphelper

import android.view.View
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SnapHelper

class OurHelper : SnapHelper() {
    //计算最终移动距离
    override fun calculateDistanceToFinalSnap(layoutManager: RecyclerView.LayoutManager, targetView: View): IntArray? {
        TODO("Not yet implemented")
    }

    //获取移动View
    override fun findSnapView(layoutManager: RecyclerView.LayoutManager?): View? {
        TODO("Not yet implemented")
    }

    //获取移动View的角标位置
    override fun findTargetSnapPosition(layoutManager: RecyclerView.LayoutManager?, velocityX: Int, velocityY: Int): Int {
        TODO("Not yet implemented")
    }
}

实践检验

RecyclerView 常规使用,仅加入了SnapHelper.attachToRecyclerView相关绑定

前置 ItemView

在这里插入图片描述

item_view

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="250dp"
    android:layout_height="100dp"
    android:paddingHorizontal="5dp">

    <TextView
        android:id="@+id/tv_data"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#f98741"
        android:gravity="center"
        android:text="Item Data"
        android:textColor="#ffffff"
        android:textStyle="bold" />
</androidx.appcompat.widget.LinearLayoutCompat>

前置 Adapter

package com.example.recyclerviewsnaphelper

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class OurAdapter(private val dataList: MutableList<String>) : RecyclerView.Adapter<OurAdapter.OurViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OurViewHolder {
        return OurViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_view, parent,false))
    }

    override fun getItemCount(): Int {
        return dataList.size
    }

    override fun onBindViewHolder(holder: OurViewHolder, position: Int) {
        holder.itemView.findViewById<TextView>(R.id.tv_data).text=dataList[position]
    }

    inner class OurViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}

使用方式

package com.example.recyclerviewsnaphelper

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSnapHelper
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.HORIZONTAL

class MainActivity : AppCompatActivity() {
    var dataList = mutableListOf<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //数据模拟
        for (i in 0..15) {
            dataList.add("第${i + 1}页")
        }
        //RecyclerView基础配置
        pagerRecyclerSetting()
        layoutRecyclerSetting()
    }

    /**
     * RecyclerView基础配置:PagerSnapHelper示例
     * */
    private fun pagerRecyclerSetting() {
        val mRvPager = findViewById<RecyclerView>(R.id.rv_pager)
        var layoutManager = LinearLayoutManager(this)
        layoutManager.orientation = HORIZONTAL
        mRvPager.layoutManager = layoutManager
        val ourPagerAdapter = OurAdapter(dataList)
        mRvPager.adapter = ourPagerAdapter
        //添加SnapHelper相关辅助类
        val pagerSnapHelper = PagerSnapHelper()
        pagerSnapHelper.attachToRecyclerView(mRvPager)
    }

    /**
     * RecyclerView基础配置:LinearSnapHelper示例
     * */
    private fun layoutRecyclerSetting() {
        val mRvLinear = findViewById<RecyclerView>(R.id.rv_linear)
        var layoutManager = LinearLayoutManager(this)
        layoutManager.orientation = HORIZONTAL
        mRvLinear.layoutManager = layoutManager
        val ourLayoutAdapter = OurAdapter(dataList)
        mRvLinear.adapter = ourLayoutAdapter

        //添加SnapHelper相关辅助类
        val lineaSnapHelper = LinearSnapHelper()
        lineaSnapHelper.attachToRecyclerView(mRvLinear)
    }
}

activity_main

  • 预览图

在这里插入图片描述

  • layout布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:gravity="center"
        android:text="PagerSnapHelper效果"
        android:textStyle="bold" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        tools:itemCount="10"
        tools:listitem="@layout/item_view" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_marginTop="50dp"
        android:gravity="center"
        android:text="LinearSnapHelper"
        android:textStyle="bold" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_linear"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
        tools:itemCount="10"
        tools:listitem="@layout/item_view" />

</androidx.appcompat.widget.LinearLayoutCompat>

项目经验

仅记录我在项目使用中遇到的问题

SnapHelper 获取居中Item位置(position)

其实在 RecyclerView 中不仅支持 通过 position获取View,也支持通过 View获取position

在这里插入图片描述

因为我们使用了SnapHelper实现居中效果,故需要用到SnapHelper中的 findSnapView(LayoutManager layoutManager) 方法 找到 最接近对齐位置的view,该view称为SanpView,对应的 position 称为 SnapPosition;(如果返回 null,就表示没有需要对齐的View,也就不会做滚动对齐调整)

在这里插入图片描述

核心逻辑

在这里插入图片描述

API 借鉴于此

        recyclerView = findViewById(R.id.recyclerview);
        // 用于每次滑动后将item居中
        SnapHelper snapHelper = new PagerSnapHelper();
        snapHelper.attachToRecyclerView(recyclerView);
        FrequencyAdapter adapter = new MyAdapter();
        adapter.setOnItemClickListener(onItemClickListener);
        recyclerView.setAdapter(adapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(this, RecyclerView.HORIZONTAL, false));
 
		//监听 滚动 获取具体位置
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                	View itemView = snapHelper.findSnapView(recyclerView.getLayoutManager());
                	position = itemView == null ? -1 : recyclerView.getChildAdapterPosition(itemView);
                    Log.e(TAG, "the current position is: " + currentPosition);
                }
            }
        });

项目伪代码

        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    val itemView = pagerSnapHelper.findSnapView(recyclerView.layoutManager)
                    var position = if (itemView == null) -1 else {
                        recyclerView.getChildAdapterPosition(itemView)
                    }
                    black?.invoke(position)
                }
            }
        })
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

远方那座山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值