RV + tabLayout吸顶 + VP + RV嵌套滑动完美方案

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){

    }


}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值