此篇文章给出在Android上用Kotlin实现ScrollView和RecyclerView的嵌套滚动。
首先看一下实现后的效果:
我们需要了解的是Android已为我们实现了ScrollView的嵌套类NestedScrollView和RecyclerView的嵌套。NestedScrollView实现了NestedScrollingParent3和NestedScrollingChild3这两个支持嵌套的接口;RecyclerView实现了NestedScrollingChild2, NestedScrollingChild3这两个支持嵌套的接口。为了实现以上效果,简单的方式就是继承以上类来自定义实现。
实际上只需要自定义NestedScrollView就行了,原理涉及到事件分发,分发原理本文不涉及。
NestedScrollViewFather类的代码:
class NestedScrollViewFather : NestedScrollView { private val TAG = "NestedScrollViewFather" private val DEBUG = false constructor(context: Context) :super(context) { init() } constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { init() } constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( context, attrs, defStyleAttr ) { init() } private lateinit var topView: View private lateinit var contentView: ViewGroup private lateinit var mFlingHelper: FlingHelper private var totalDy = 0 /** * 用于判断RecyclerView是否在fling */ private var isStartingFling = false /** * 记录当前滑动的y轴加速度 */ private var velocityY = 0 private fun init() { mFlingHelper = FlingHelper(context) setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> if (isStartingFling) { totalDy = 0 isStartingFling = false } if(0 == scrollY) { if (DEBUG) { Log.d(TAG, "TOP SCROLL") } } if (scrollY == (getChildAt(0).measuredHeight - v.measuredHeight)) { if (DEBUG) { Log.d(TAG, "BOTTOM SCROLL") } dispatchChildFling() } //在RecyclerView fling情况下,记录当前RecyclerView在y轴的偏移 totalDy += scrollY - oldScrollY } } private fun dispatchChildFling() { if (0 != velocityY) { val splineFlingDistance = mFlingHelper.getSplineFlingDistance(velocityY) if (splineFlingDistance > totalDy) { childFling(mFlingHelper.getVelocityByDistance(splineFlingDistance - totalDy)) } } totalDy = 0 velocityY = 0 } private fun childFling(velY: Int) { getChildRecyclerView(contentView)?.fling(0, velY) } override fun fling(velocityY: Int) { super.fling(velocityY) if (velocityY <= 0) { this.velocityY = 0 } else { isStartingFling = true this.velocityY = velocityY } } override fun onFinishInflate() { super.onFinishInflate() topView = (getChildAt(0) as ViewGroup).getChildAt(0) contentView = (getChildAt(0) as ViewGroup).getChildAt(1) as ViewGroup } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { super.onMeasure(widthMeasureSpec, heightMeasureSpec) val lp = contentView.layoutParams!!.also { it.height = measuredHeight } if (DEBUG) { Log.e(TAG, "lp.height=${lp.height}") } contentView.layoutParams = lp } override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) { val hideTop = dy > 0 && scrollY < topView.measuredHeight if (hideTop) { scrollBy(0, dy) consumed[1] = dy } } companion object { fun getChildRecyclerView(viewGroup: ViewGroup) : RecyclerView? { for (i in 0 until viewGroup.childCount) { val view = viewGroup.getChildAt(i) if (view is NestLogRecyclerView) { return view } else if (view is ViewGroup) { return getChildRecyclerView(view) } } return null } } }
列出速度与距离换算的工具类:
class FlingHelper constructor(context: Context) { companion object { private val DECELERATION_RATE = ln(.78) / ln(.9) private val mFlingFriction = ViewConfiguration.getScrollFriction() } private var mPhysicalCoeff = context.resources.displayMetrics.density * 160 * 386.0878f * .84f private fun getSplineDeceleration(i: Int): Double { return ln((.35 * abs(i)) / (mFlingFriction * mPhysicalCoeff)) } private fun getSplineDecelerationByDistance(d: Double) : Double { return (DECELERATION_RATE - 1) * ln((d / (mFlingFriction * mPhysicalCoeff))) / DECELERATION_RATE } /** * 速度 转换成 距离 */ fun getSplineFlingDistance(i: Int): Double { return exp(getSplineDeceleration(i) * (DECELERATION_RATE / (DECELERATION_RATE - 1)) * mFlingFriction * mPhysicalCoeff) } /** * 距离 转换成 速度 */ fun getVelocityByDistance(d: Double): Int { return abs(exp(getSplineDecelerationByDistance(d)) * mFlingFriction * mPhysicalCoeff / .3499999940395355).toInt() } }
配套的XML布局文件代码:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/conflict_root" android:layout_width="match_parent" android:layout_height="match_parent"> <com.tikeda.binley.conflictdemo.nested.NestedScrollViewFather android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="60dp" android:textSize="24sp" android:textColor="@color/black" android:text="这是第一个" android:gravity="center"/> <TextView android:layout_width="match_parent" android:layout_height="60dp" android:textSize="24sp" android:textColor="@color/black" android:text="这是第二个" android:gravity="center"/> <TextView android:layout_width="match_parent" android:layout_height="60dp" android:textSize="24sp" android:textColor="@color/black" android:text="这是第三个" android:gravity="center"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="46dp" android:orientation="horizontal" android:background="@color/purple_200"> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:text="表1" android:textColor="@color/black" android:gravity="center" android:layout_weight="1"/> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:text="表2" android:textColor="@color/black" android:gravity="center" android:layout_weight="1"/> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:text="表3" android:textColor="@color/black" android:gravity="center" android:layout_weight="1"/> </LinearLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rv_demo" android:layout_width="match_parent" android:layout_height="match_parent"/> </LinearLayout> </LinearLayout> </com.tikeda.binley.conflictdemo.nested.NestedScrollViewFather> </LinearLayout>
NestedScrollViewFather由于内部写死了获取topView和contentView的规则,所以仅针对给出的XML代码有效
标签:velocityY,return,Kotlin,private,ScrollView,context,fun,RecyclerView From: https://www.cnblogs.com/swalka/p/16815187.html