项目改版需要实现一个圆弧效果的滑动列表,网上没找到很好的开源实现,自己改了一版,给出具体实现步骤和源码,以下是项目实现效果截图。
一、实现步骤
要求:当前选中的项要居中显示,总共要显示5个可见列表项,前后要各填充两个空数据,列表要以圆弧效果滚动,滑动停止之后要进行居中校正显示处理。
思路:监听列表滑动事件,在滑动的时候动态计算设置每个列表项距离顶部的距离,在滑动停止之后获取当前第一个可见列表项,平滑滚动到居中位置。
这个只是一个简单的基础数学题,滑动的时候计算设置GF两点的距离值即可,整个RecyclerView列表当做圆的一部分弧形区域。
1. 列表滚动监听
在onScrolled方法中监听滚动,动态计算每个列表项需要距离顶部的值,来实现滑动的时候圆弧滚动效果。在onScrollStateChanged方法中,监听SCROLL_STATE_IDLE状态,进行居中校正。
mRVList?.addOnScrollListener(object : RecyclerView.OnScrollListener() { var isManualScroll = false override fun onScrollStateChanged(@NonNull recyclerView: RecyclerView, newState: Int) { super.onScrollStateChanged(recyclerView, newState) when (newState) { RecyclerView.SCROLL_STATE_IDLE -> { if (isManualScroll) { var manager = recyclerView.layoutManager as LinearLayoutManager var firstVisibleItemPosition = manager.findFirstVisibleItemPosition() syncShow(firstVisibleItemPosition) } isManualScroll = false } RecyclerView.SCROLL_STATE_DRAGGING -> { isManualScroll = true } } } override fun onScrolled(@NonNull recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) for (i in 0 until recyclerView.childCount) { val paddingTop: Int = calculateMargin(recyclerView.getChildAt(i).left) recyclerView.getChildAt(i).setPadding(0, paddingTop, 0, 0) } } })
private fun calculateMargin(marginLeft: Int): Int { val display = resources.displayMetrics val widthPixel = display.widthPixels.toDouble() val halfItemWidth = mArcAdapter?.dp2px(20f) ?:0 // 偏离中点距离(AH) val offset = Math.abs((widthPixel / 2).minus(marginLeft.plus(halfItemWidth))) // 直角三角形一条直角边(OG) val height = Math.sqrt(Math.pow(widthPixel * 2, 2.0) - Math.pow(offset, 2.0)) // 距离顶部的值就是圆弧半径(OH)-直角边(OG) val marginTop = widthPixel * 2 - height return marginTop.toInt() }
上面的代码中,列表项宽度设置为40dp,圆弧的半径是屏幕宽度的两倍,参数根据自己实际需求调整即可。
2. 居中校正平滑滚动处理
实现设置LinearSmoothScroller对象,calculateTimeForScrolling设置滚动的时间,单位毫秒,让滚动更平滑。
private fun syncShow(position: Int) { mRVList?.smoothSnapToPosition(position) mArcAdapter?.firstVisiblePosition = position mArcAdapter?.notifyDataSetChanged() } fun RecyclerView.smoothSnapToPosition( position: Int, snapMode: Int = LinearSmoothScroller.SNAP_TO_START ) { val smoothScroller = object : LinearSmoothScroller(this.context) { override fun getVerticalSnapPreference(): Int = snapMode override fun getHorizontalSnapPreference(): Int = snapMode override fun calculateTimeForScrolling(dx: Int): Int { return 250 } } smoothScroller.targetPosition = position layoutManager?.startSmoothScroll(smoothScroller) }
3. 点击列表项设置居中
增加点击列表项的时候的居中校正处理,保证选中项始终显示在中间位置。
object: AdapterView.OnItemClickListener { override fun onItemClick(p0: AdapterView<*>?, p1: View?, position: Int, p3: Long) { if (position > 1 && position < (mArcAdapter?.list?.size ?:0 - 2)) { syncShow(position - 2) } } }
RecyclerView并没有setOnItemClickListener接口,需要在ViewHolder中实现
inner class ItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener { val mTvTitle: TextView = itemView.findViewById(R.id.mTvTitle) init { itemView.setOnClickListener(this) } override fun onClick(view: View?) { onItemClickListener?.onItemClick(null, view, getAdapterPosition(), (view?.id ?:0).toLong()); } }
二、项目源码
https://github.com/wenguan0927/ArcRecyclerView
扩展阅读:
转载请注明出处:陈文管的博客 – Android 弧形RecyclerView实现
微信公众号 扫一扫关注