Android LinearSnapHelper+RecyclerView实现滚动列表固定卡在第一个Item
前言
先上效果图
看图能猜到什么效果吧,就是一直选中最左侧的日期,懒得截GIF了。
LinearSnapHelper+RecyclerView
理论上需要使用LinearSnapHelper+RecyclerView的,但是想偷个懒,直接使用做好的轮子。
https://github.com/TakuSemba/MultiSnapRecyclerView
使用方法:
dependencies {
implementation 'com.github.takusemba:multisnaprecyclerview:2.0.0'
}
然后在布局中添加:
<com.takusemba.multisnaprecyclerview.MultiSnapRecyclerView
android:id="@+id/rcv_day"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:visibility="@{isShowCalendar? View.GONE: View.VISIBLE}"
app:msrv_gravity="start"
app:msrv_interval="1"
app:msrv_speed_ms_per_inch="300"
tools:listitem="@layout/item_main_snap_day" />
再写一个Adapter,这个部分大家都会,只当参考哈
class MainSnapDayAdapter constructor(
var mHandler: Handler? = null,
var mList: MutableList<SnapDayInfo> = mutableListOf()
): RecyclerView.Adapter<MainSnapDayAdapter.ViewHolder>() {
private var mSelectDay: Int = 0
private var mHasDataDayList: MutableList<Int> = mutableListOf()
fun setSelectDay(selectDay: Int) {
this.mSelectDay = selectDay
notifyDataSetChanged()
}
fun setHasDataDayList(dayList: List<Int>) {
mHasDataDayList.clear()
mHasDataDayList.addAll(dayList)
}
fun update(list: MutableList<SnapDayInfo>, selectDay: Int) {
this.mList = list
this.mSelectDay = selectDay
notifyDataSetChanged()
}
data class SnapDayInfo(
var day: Int,
var lunar: String
): Serializable
inner class ViewHolder constructor(
val binding: ItemMainSnapDayBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(info: SnapDayInfo) {
binding.dayInfo = info
binding.isSelected = mSelectDay == info.day
binding.isHasData = mHasDataDayList.contains(info.day)
binding.executePendingBindings()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: ItemMainSnapDayBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context), R.layout.item_main_snap_day,
parent, false)
return ViewHolder(binding)
}
override fun getItemCount(): Int = this.mList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val info = this.mList[position]
holder.bind(info)
}
}
最后在Fragment中说使用
// 日期列表
binding.rcvDay.layoutManager = LinearLayoutManager(requireContext(),
LinearLayoutManager.HORIZONTAL, false)
binding.rcvDay.adapter = dayAdapter
// 滚动选中事件
binding.rcvDay.setOnSnapListener(object : OnSnapListener{
override fun snapped(position: Int) {
binding.selectDay = position + 1
dayAdapter.setSelectDay(binding.selectDay)
binding.calendarView.scrollToCalendar(binding.selectYear, binding.selectMonth, binding.selectDay, false, false)
updateSelectMember(binding.memberInfo?: return)
}
})
refreshDayList()
重点来啦
最主要的就是最后一个item永远滚不到第一个来,在这里我卡了好久,后来同事提出的想法就是将最后一个item的宽度占recyclerview的宽度
/** 刷新日期列表 */
private fun refreshDayList() {
val list = mutableListOf<MainSnapDayAdapter.SnapDayInfo>()
for (i in 1 .. DateUtils.getDaysOfMonth(binding.selectYear, binding.selectMonth)) {
list.add(MainSnapDayAdapter.SnapDayInfo(i, DateUtils.getLunarString(binding.selectYear, binding.selectMonth, i)))
}
val day = binding.selectDay
dayAdapter.update(list, day)
// 滚动到选中日期
val scroll = TopSmoothScroller(requireContext())
scroll.targetPosition = day -1
binding.rcvDay.layoutManager?.startSmoothScroll(scroll)
// 清除最后一个数字的"边线样式",避免边线样式不断叠加
if (binding.rcvDay.itemDecorationCount > 0) {
binding.rcvDay.removeItemDecorationAt(binding.rcvDay.itemDecorationCount -1)
}
binding.rcvDay.addItemDecoration(object : RecyclerView.ItemDecoration(){
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
val position = (view.layoutParams as RecyclerView.LayoutParams).viewLayoutPosition
if (position == list.size -1) {
// 给最后一个item增加宽度
outRect.right = parent.width - view.width
}
}
})
}
TopSmoothScroller
/**
* RecyclerView 滚动到POSITION时到顶部
* @author D10NG
* @date on 2020/6/17 11:57 AM
*/
class TopSmoothScroller constructor(
context: Context
) : LinearSmoothScroller(context) {
override fun getHorizontalSnapPreference(): Int {
return SNAP_TO_START
}
override fun getVerticalSnapPreference(): Int {
return SNAP_TO_START
}
}