package com.app.myapplication
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import androidx.recyclerview.widget.RecyclerView
import com.app.myapplication.utils.FlingHelper
import com.app.myapplication.utils.UIUtils
open class ChildRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
RecyclerView(context, attrs, defStyleAttr) {
private val mFlingHelper = FlingHelper(context)
private var mMaxDistance = 0
private var mVelocityY = 0
var isStartFling: Boolean = false
var totalDy: Int = 0
var mParentRecyclerView: ParentRecyclerView? = null
init {
mMaxDistance = mFlingHelper.getVelocityByDistance((UIUtils.getScreenHeight() * 4).toDouble())
overScrollMode = RecyclerView.OVER_SCROLL_NEVER
initScrollListener()
}
private fun initScrollListener() {
addOnScrollListener(object :OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if(isStartFling) {
totalDy = 0
isStartFling = false
}
totalDy += dy
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if(newState == RecyclerView.SCROLL_STATE_IDLE) {
dispatchParentFling()
}
super.onScrollStateChanged(recyclerView, newState)
}
})
}
private fun dispatchParentFling() {
mParentRecyclerView = findParentRecyclerView()
mParentRecyclerView?.run {
if(isScrollTop() && mVelocityY != 0) {
//当前ChildRecyclerView已经滑动到顶部,且竖直方向加速度不为0,如果有多余的需要交由父RecyclerView继续fling
val flingDistance = mFlingHelper.getSplineFlingDistance(mVelocityY)
if(flingDistance > (Math.abs(this@ChildRecyclerView.totalDy))) {
fling(0,-mFlingHelper.getVelocityByDistance(flingDistance + this@ChildRecyclerView.totalDy))
}
//fix 在run方法里面,注意 this@ChildRecyclerView的使用,否则使用的是ParentRecyclerView的变量
this@ChildRecyclerView.totalDy = 0
mVelocityY = 0
}
}
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
if(ev != null && ev.action == MotionEvent.ACTION_DOWN) {
mVelocityY = 0
}
return super.dispatchTouchEvent(ev)
}
override fun fling(velocityX: Int, velocityY: Int): Boolean {
if(isAttachedToWindow.not()) return false
val fling = super.fling(velocityX, velocityY)
if(!fling || velocityY >= 0) {
//fling为false表示加速度达不到fling的要求,将mVelocityY重置
mVelocityY = 0
} else {
//正在进行fling
isStartFling = true
mVelocityY = velocityY
}
return fling
}
fun isScrollTop(): Boolean {
//RecyclerView.canScrollVertically(-1)的值表示是否能向下滚动,false表示已经滚动到顶部
return !canScrollVertically(-1)
}
private fun findParentRecyclerView(): ParentRecyclerView? {
var parentView = parent
while ((parentView is ParentRecyclerView).not()) {
parentView = parentView.parent
}
return parentView as? ParentRecyclerView
}
}
package com.app.myapplication
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.app.myapplication.adapter.MultiTypeAdapter
import com.app.myapplication.utils.FlingHelper
import com.app.myapplication.utils.UIUtils
import java.util.concurrent.atomic.AtomicBoolean
class ParentRecyclerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
RecyclerView(context, attrs, defStyleAttr) {
private var mMaxDistance:Int = 0
private val mFlingHelper = FlingHelper(context)
/**
* 记录上次Event事件的y坐标
*/
private var lastY:Float = 0f
var totalDy = 0
/**
* 用于判断RecyclerView是否在fling
*/
var isStartFling = false
/**
* 记录当前滑动的y轴加速度
*/
private var velocityY: Int = 0
var canScrollVertically: AtomicBoolean
init {
mMaxDistance = mFlingHelper.getVelocityByDistance((UIUtils.getScreenHeight() * 4).toDouble())
canScrollVertically = AtomicBoolean(true)
addOnScrollListener(object :OnScrollListener(){
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
//如果父RecyclerView fling过程中已经到底部,需要让子RecyclerView滑动神域的fling
if(newState == RecyclerView.SCROLL_STATE_IDLE) {
dispatchChildFling()
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if(isStartFling) {
totalDy = 0
isStartFling = false
}
//在RecyclerView fling情况下,记录当前RecyclerView在y轴的偏移
totalDy += dy
}
})
}
private fun dispatchChildFling() {
if (isScrollEnd() && velocityY != 0) {
val splineFlingDistance = mFlingHelper.getSplineFlingDistance(velocityY)
if (splineFlingDistance > totalDy) {
childFling(mFlingHelper.getVelocityByDistance(splineFlingDistance - totalDy.toDouble()))
}
}
totalDy = 0
velocityY = 0
}
private fun childFling(velY: Int) {
findNestedScrollingChildRecyclerView()?.fling(0,velY)
}
fun initLayoutManager() {
val linearLayoutManager = object : LinearLayoutManager(context) {
override fun scrollVerticallyBy(dy: Int, recycler: Recycler?, state: State?): Int {
return try {
super.scrollVerticallyBy(dy, recycler, state)
} catch (e: Exception) {
e.printStackTrace()
0
}
}
override fun onLayoutChildren(recycler: Recycler?, state: State?) {
try {
super.onLayoutChildren(recycler, state)
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun canScrollVertically(): Boolean {
val childRecyclerView = findNestedScrollingChildRecyclerView()
return canScrollVertically.get() || childRecyclerView == null || childRecyclerView.isScrollTop()
}
override fun addDisappearingView(child: View?) {
try {
super.addDisappearingView(child)
} catch (e: Exception) {
e.printStackTrace()
}
}
override fun supportsPredictiveItemAnimations(): Boolean {
return false
}
}
linearLayoutManager.orientation = LinearLayoutManager.VERTICAL
layoutManager = linearLayoutManager
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
if(ev != null && ev.action == MotionEvent.ACTION_DOWN) {
//ACTION_DOWN的时候重置加速度
velocityY = 0
stopScroll()
}
if((ev == null || ev.action == MotionEvent.ACTION_MOVE).not()) {
//在非ACTION_MOVE的情况下,将lastY置为0
lastY = 0f
}
return try {
super.dispatchTouchEvent(ev)
} catch (e: Exception) {
e.printStackTrace()
false
}
}
override fun onTouchEvent(e: MotionEvent): Boolean {
if(lastY == 0f) {
lastY = e.y
}
if(isScrollEnd()) {
//如果父RecyclerView已经滑动到底部,需要让子RecyclerView滑动剩余的距离
val childRecyclerView = findNestedScrollingChildRecyclerView()
childRecyclerView?.run {
val deltaY = (lastY - e.y).toInt()
canScrollVertically.set(false)
scrollBy(0,deltaY)
}
}
if(e.action == MotionEvent.ACTION_UP) {
canScrollVertically.set(true)
}
lastY = e.y
return try {
super.onTouchEvent(e)
} catch (e: Exception) {
e.printStackTrace()
false
}
}
override fun fling(velX: Int, velY: Int): Boolean {
val fling = super.fling(velX, velY)
if (!fling || velY <= 0) {
velocityY = 0
} else {
isStartFling = true
velocityY = velY
}
return fling
}
private fun isScrollEnd(): Boolean {
//RecyclerView.canScrollVertically(1)的值表示是否能向上滚动,false表示已经滚动到底部
return !canScrollVertically(1)
}
private fun findNestedScrollingChildRecyclerView(): ChildRecyclerView? {
(adapter as? MultiTypeAdapter)?.apply {
return getCurrentChildRecyclerView()
}
return null
}
override fun scrollToPosition(position: Int) {
//处理一键置顶会出现卡顿的问题
findNestedScrollingChildRecyclerView()?.scrollToPosition(position)
postDelayed({
super.scrollToPosition(position)
},50)
}
//----------------------------------------------------------------------------------------------
// NestedScroll. fix:当ChildRecyclerView下滑时(手指未放开),ChildRecyclerView滑动到顶部(非fling),此时ParentRecyclerView不会继续下滑。
//----------------------------------------------------------------------------------------------
override fun onStartNestedScroll(child: View?, target: View?, nestedScrollAxes: Int): Boolean {
return (target != null) && target is ChildRecyclerView
}
override fun onNestedPreScroll(target: View?, dx: Int, dy: Int, consumed: IntArray) {
val childRecyclerView = findNestedScrollingChildRecyclerView()
//1.当前Parent RecyclerView没有滑动底,且dy> 0 是下滑
val isParentCanScroll = dy > 0 && isScrollEnd().not()
//2.当前Child RecyclerView滑到顶部了,且dy < 0,即上滑
val isChildCanNotScroll = !(dy >= 0
|| childRecyclerView == null
|| childRecyclerView.isScrollTop().not())
//以上两种情况都需要让Parent RecyclerView去scroll,和下面onNestedPreFling机制类似
if(isParentCanScroll || isChildCanNotScroll) {
scrollBy(0,dy)
consumed[1] = dy
}
}
override fun onNestedFling(target: View?, velocityX: Float, velocityY: Float, consumed: Boolean): Boolean {
return true
}
override fun onNestedPreFling(target: View?, velocityX: Float, velocityY: Float): Boolean {
val childRecyclerView = findNestedScrollingChildRecyclerView()
val isParentCanFling = velocityY > 0f && isScrollEnd().not()
val isChildCanNotFling = !(velocityY >= 0
|| childRecyclerView == null
|| childRecyclerView.isScrollTop().not())
if(isParentCanFling.not() && isChildCanNotFling.not()) {
return false
}
fling(0,velocityY.toInt())
return true
}
fun isChildRecyclerViewCanScrollUp(): Boolean {
return findNestedScrollingChildRecyclerView()?.isScrollTop()?.not() ?: false
}
//----------------------------------------------------------------------------------------------
// NestedScroll. fix:当ChildRecyclerView下滑时(手指未放开),ChildRecyclerView滑动到顶部(非fling),此时ParentRecyclerView不会继续下滑。
//----------------------------------------------------------------------------------------------
private var oldX = 0F
private var oldY = 0F
private var newX = 0F
private var newY = 0F
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
val intercept = super.onInterceptTouchEvent(e)
if (e != null) {
val action = e.action
when (action) {
MotionEvent.ACTION_DOWN -> run {
oldX = e.x
oldY = e.y
Log.d("dispatchTouchEvent", "parent ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> run {
newX = e.x
newY = e.y
Log.d(
"onInterceptTouchEvent",
"onInterceptTouchEvent parent oldX:" + oldX + ",oldY:" + oldY
)
Log.d(
"onInterceptTouchEvent",
"onInterceptTouchEvent parent newX:" + newX + ",newY:" + newY
)
oldX = newX
oldY = newY
}
MotionEvent.ACTION_CANCEL -> run {
Log.d("dispatchTouchEvent", "parent ACTION_CANCEL")
}
MotionEvent.ACTION_UP -> run {
Log.d("dispatchTouchEvent", "parent ACTION_UP")
}
}
}
val childRecyclerView: RecyclerView? = findNestedScrollingChildRecyclerView()
if (childRecyclerView != null) {
val isScrollUp = !childRecyclerView.canScrollVertically(-1)
if (isScrollUp) {
Log.d("dispatchTouchEvent", "parent 子滑动到顶部")
} else {
Log.d("dispatchTouchEvent", "parent 子没有滑动到顶部,可以继续向上滑动")
/*
不再这里去处理手势了,放在子中,不用去查询view,提高效率
如果x轴偏移量大于y轴此时父控件先去处理,之后再交给子控件
*/
return if (Math.abs(newX - oldX) > Math.abs(newY - oldY)) {
intercept
} else {
false
}
}
}
if (intercept) {
Log.d("dispatchTouchEvent", "parent 父控件拦截手势了")
} else {
Log.d("dispatchTouchEvent", "parent 父控件未拦截手势")
}
return intercept
}
}
package com.app.myapplication.utils
import android.content.Context
import android.view.ViewConfiguration
import kotlin.math.abs
import kotlin.math.exp
import kotlin.math.ln
/**
* Fling 帮助类
*/
class FlingHelper(context: Context) {
private var mPhysicalCoeff: Float = context.resources.displayMetrics.density * 160.0f * 386.0878f * 0.84f
private fun getSplineDeceleration(i: Int): Double {
return ln((0.35f * abs(i).toFloat() / (mFlingFriction * mPhysicalCoeff)).toDouble())
}
private fun getSplineDecelerationByDistance(d: Double): Double {
return (DECELERATION_RATE.toDouble() - 1.0) * ln(d / (mFlingFriction * mPhysicalCoeff).toDouble()) / DECELERATION_RATE.toDouble()
}
/**
* 根据加速度来获取需要fling的距离
* @param i 加速度
* @return fling的距离
*/
fun getSplineFlingDistance(i: Int): Double {
return exp(getSplineDeceleration(i) * (DECELERATION_RATE.toDouble() / (DECELERATION_RATE.toDouble() - 1.0))) * (mFlingFriction * mPhysicalCoeff).toDouble()
}
/**
* 根据距离来获取加速度
* @param d 距离
* @return 返回加速度
*/
fun getVelocityByDistance(d: Double): Int {
return abs((exp(getSplineDecelerationByDistance(d)) * mFlingFriction.toDouble() * mPhysicalCoeff.toDouble() / 0.3499999940395355).toInt())
}
companion object {
private val DECELERATION_RATE = (ln(0.78) / ln(0.9)).toFloat()
private val mFlingFriction = ViewConfiguration.getScrollFriction()
}
}
package com.gaohui.nestedrecyclerview.kotlin
import android.content.Context
import android.support.v4.widget.SwipeRefreshLayout
import android.util.AttributeSet
class StoreSwipeRefreshLayout : SwipeRefreshLayout {
private var mParentRecyclerView: ParentRecyclerView? = null
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (mParentRecyclerView == null) {
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child is ParentRecyclerView) {
mParentRecyclerView = child
break
}
}
}
}
override fun canChildScrollUp(): Boolean {
return super.canChildScrollUp() || mParentRecyclerView?.isChildRecyclerViewCanScrollUp()?:false
}
}
参考适配器
package com.app.myapplication.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import com.app.myapplication.ChildRecyclerView
import com.app.myapplication.ListFragment
import com.app.myapplication.R
import com.google.android.material.tabs.TabLayout
import java.util.logging.Handler
class MultiTypeAdapter(private val dataSet:ArrayList<Any>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
private const val TYPE_HEADER = 0
private const val TYPE_VP = 1
private const val TYPE_Center = 2
}
private var mCategoryViewHolder: SimpleCategoryViewHolder? = null
override fun getItemViewType(position: Int): Int {
return if(dataSet[position]==1) {
TYPE_HEADER
} else if(dataSet[position]==2){
TYPE_Center
}else{
TYPE_VP
}
}
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if(viewType == TYPE_HEADER) {
SimpleHeaderHolder(
LayoutInflater.from(
viewGroup.context
).inflate(R.layout.item_header_layout, viewGroup, false)
)
} else if(viewType==TYPE_Center){
return object :RecyclerView.ViewHolder(LayoutInflater.from(viewGroup.context).inflate(
R.layout.item_center_layout,
viewGroup,
false
)){
}
}else{
val categoryViewHolder =
SimpleCategoryViewHolder(
LayoutInflater.from(viewGroup.context).inflate(
R.layout.item_vp_layout,
viewGroup,
false
)
)
mCategoryViewHolder = categoryViewHolder
return categoryViewHolder
}
}
override fun getItemCount(): Int = dataSet.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, pos: Int) {
if(holder is SimpleHeaderHolder) {
} else if(holder is SimpleCategoryViewHolder){
holder.bindData()
}
}
fun getCurrentChildRecyclerView(): ChildRecyclerView? {
mCategoryViewHolder?.apply {
return this.getCurrentChildRecyclerView()
}
return null
}
inner class SimpleCategoryViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val tlLayout by lazy { itemView.findViewById<TabLayout>(R.id.tl_layout) }
val vp by lazy { itemView.findViewById<ViewPager>(R.id.vp) }
var adapter:VPAdapter? = null
val fragments by lazy { arrayListOf<Fragment>() }
var mCurrentRecyclerView : ChildRecyclerView? = null
fun bindData(){
initTab()
initVp()
initVpWithTab()
}
private fun initVpWithTab() {
tlLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener{
override fun onTabSelected(tab: TabLayout.Tab?) {
vp.currentItem = tab?.position?:0
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
}
override fun onTabReselected(tab: TabLayout.Tab?) {
}
})
vp.addOnPageChangeListener(object : ViewPager.OnPageChangeListener{
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
}
override fun onPageSelected(position: Int) {
tlLayout.selectTab(tlLayout.getTabAt(position))
mCurrentRecyclerView = (fragments[position] as ListFragment).getChildRv()
}
override fun onPageScrollStateChanged(state: Int) {
}
})
}
private fun initVp() {
val activity = itemView.context as AppCompatActivity
adapter = VPAdapter(activity.supportFragmentManager,fragments)
vp.adapter = adapter
android.os.Handler().postDelayed(Runnable {
mCurrentRecyclerView = (fragments[0] as ListFragment).getChildRv()
},1000L)
}
private fun initTab() {
for(i in 0 until 3) {
tlLayout.addTab(tlLayout.newTab().apply {
text = "智利推荐"+i.toString()
})
fragments.add(ListFragment.newInstance(i.toString(),""))
}
}
fun getCurrentChildRecyclerView(): ChildRecyclerView? {
return mCurrentRecyclerView
}
}
inner class SimpleHeaderHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
}
}