GithubHelp home page GithubHelp logo

xmusistone / persistentrecyclerview Goto Github PK

View Code? Open in Web Editor NEW
313.0 5.0 37.0 23.7 MB

京东首页 - 长列表内嵌ViewPager商品流(RecyclerView方案)

License: Apache License 2.0

Kotlin 65.93% Java 34.07%

persistentrecyclerview's Introduction

PersistentRecyclerView

仿京东首页,整体是个长列表(ParentRecyclerView),内嵌子列表 - 商品feeds流(ChildRecyclerView),且商品流可以左右滑动。

实现效果

点击可查看截屏视频

使用方法

  1. 外部的长列表使用ParentRecyclerView;
  2. 内嵌的子列表使用ChildRecyclerView;

Adapter及ViewHolder跟官方Recyclerview一样,ViewPager和ViewPager2可随意选用,均已内部兼容;

实现原理

通过uiautomatorviewer观察京东首页的View层级,会发现其长列表总体是个RecyclerView,设为ParentRecyclerView;而底部的商品feeds流是另一个Recyclerview,设为ChildRecyclerView。关键要解决这2个问题:

问题一:ParentRecyclerView触底时,Fling速率传递给ChildRecyclerView;
问题二:ChildRecyclerView触顶时,Fling速率传递给ParentRecyclerView;

这两个问题,都避不开一个问题,即:如何获取当前RecyclerView的Fling速率?

在阅读RecyclerView源码后,发现RecyclerView内部保存了一个mViewFlinger对象,而mViewFlinger内部持有OverScroller。于是,获取当前RecyclerView的Fling速率便迎刃而解:

private val overScroller: OverScroller

init {
    // 1. mViewFlinger对象获取
    val viewFlingField = RecyclerView::class.java.getDeclaredField("mViewFlinger")
    viewFlingField.isAccessible = true
    var viewFlingObj = viewFlingField.get(this)

    // 2. overScroller对象获取
    val overScrollerFiled = viewFlingObj.javaClass.getDeclaredField("mOverScroller")
    overScrollerFiled.isAccessible = true
    overScroller = overScrollerFiled.get(viewFlingObj) as OverScroller
}

/**
 * 获取垂直方向的速率
 */
fun getVelocityY(): Int = (overScroller.currVelocity).toInt()

拿到当前RecyclerView的Fling速率之后,接下来就是将Fling速率传递给另一个RecyclerView了!这个比较简单,因为RecyclerView对外开放了fling()方法,可直接调用:

/**
 * Begin a standard fling with an initial velocity along each axis in pixels per second.
 * If the velocity given is below the system-defined minimum this method will return false
 * and no fling will occur.
 *
 * @param velocityX Initial horizontal velocity in pixels per second
 * @param velocityY Initial vertical velocity in pixels per second
 * @return true if the fling was started, false if the velocity was too low to fling or
 * LayoutManager does not support scrolling in the axis fling is issued.
 */
public boolean fling(int velocityX, int velocityY)

看起来好简单,就这么结束了?

当然不是!

上面的问题一还要解决另一个问题:ParentRecyclerView如何找到ViewPager.currentItem对应的ChildRecyclerView?

ChildRecyclerView可以通过getParent()找到ParentRecyclerView,但是ParentRecyclerView如何找到ChildRecyclerView呢?现在摆在我们面前的是,Parent和Child之间至少还隔了一层ViewPager(或ViewPager2)!如果布局再复杂一些,他们中间可能还隔着若干层其它的ViewGroup!

我们都知道,ParentRecyclerView、ViewPager/ViewPager2、ChildRecyclerView三者的关系是1:1:N,于是可以想到这两点:

  • ParentRecyclerView寻找ChildRecyclerView是不是可以通过ViewPager来代理?
  • ViewPager/ViewPager2如何找到当前currentItem对应的子View?子View如何找到下面的ChildRecyclerView?

于是乎,ParentRecyclerView寻找ChildRecyclerView的方案就来了:

/**
 * ParentRecyclerView获取当前的ChildRecyclerView(只贴出了ViewPager2对应的代码)
 */
private fun findCurrentChildRecyclerView(): ChildRecyclerView? {
    if (innerViewPager2 != null) {
        // 1. 获取当前的子View
        val layoutManagerFiled = ViewPager2::class.java.getDeclaredField("mLayoutManager")
        layoutManagerFiled.isAccessible = true
        val pagerLayoutManager = layoutManagerFiled.get(innerViewPager2) as LinearLayoutManager
        var currentChild = pagerLayoutManager.findViewByPosition(innerViewPager2!!.currentItem)
        
        // 2. 从子View中获取ChildRecyclerView
        if (currentChild is ChildRecyclerView) {
            return currentChild
        } else {
            // 这个tag是ChildRecyclerView保存的
            val tagView = currentChild?.getTag(R.id.tag_saved_child_recycler_view)
            if (tagView is ChildRecyclerView) {
                return tagView
            }
        }
    }
}

// ChildRecyclerView相关代码略

然后?这样就可以了是么?

当然不是!

上述的种种,仅仅处理了Fling传导的情形,我们还需要让ParentRecyclerView实现NestedScrollingParent3,借力安卓官方的思路,实现内联滑动:

/**
 * ParentRecyclerView消费多少dy?
 **/
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
    if (target is ChildRecyclerView) {
        // 根据当前滑动位置及状态,判断自己需要消费多少dy
        // 详细代码略
    }
}

RecyclerView嵌套子列表,原理大体如此,内部做了很友好的封装,调用侧的约束特别少!当然,代码中还有一些其它的巧妙设定,比如stickyHeight、childPagerContainer等,限于篇幅问题,此处就不再赘述了!

另一种方案

对于长列表内嵌ViewPager以及ChildRecyclerView,官方控件中最接近这种效果的是CoordinatorLayout。所以,CoordinatorLayout改造之后,也能实现这样的效果,感兴趣的同学可去瞅瞅:传送门

Demo下载

点击下载

persistentrecyclerview's People

Contributors

xmusistone avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

persistentrecyclerview's Issues

可以用support包下面的recyclerView跟viewPager吗?

问题:可以用support包下面的recyclerView跟viewPager吗?
描述:我换完了support包下的recyclerView跟viewPager(包括反射的一些方法修改),我的项目发现parentRecyclerView滑动很快的时候是没问题的,如果parentRecyclerView跟childRecyclerView同时在屏幕时,滑动childRecyclerView时只滑动childRecyclerView,而外层不动;
分析:不知道是不是我用的不是androidx下面的recycleview跟viewpager导致的,还有ParentRecycleView我用了淘宝的v-layout的VirtualLayoutManager;希望能得到作者的指点,同时感谢作者提供的方案。

高版本滑动惯性不能用

android 11,子recycleview,的惯性传递一半就不能滑动了,应该是反射禁用问题,大神又解决方法没

华为Mate 20手机的bug

你好,我在华为Mate 20手机上(EMUI10.1.0、Androd 10),页面滑到最底部,点击屏幕,页面会不自然的向上滚动一段距离。不知道这是什么原因。

建议

作者写的这个非常棒,滑动效果也非常牛逼,滑动置顶的如京东的tablayout吧变换样式这个该怎么处理呢

外层联动有问题

最外层滑动到吸顶位置,就停了,内层可以滑动,外层无法和内层联动

启用SmartRefreshLayout的LoadMore导致列表无法正常上滑

Hi,作者你好,你实现的这个思路学习了,非常好。
在学习过程中,发现一个问题:
main_refresh_layout.setOnLoadMoreListener {
uiHandler?.sendEmptyMessageDelayed(MSG_TYPE_LOAD_MORE, 500L)
}
启用LoadMore后,整个列表在商品feeds流数据滑动到顶部后,就无法继续滑动了,因为直接出发了SmartRefreshLayout的LoadMore事件,排查下来,是由于SmartRefreshLayout的dispatchTouchEvent事件分发函数中,判定已经达到了LoadMore事件的出发条件,猜测是因为ParentRecyclerView中的onNestedPreScroll函数
if (consumeY != 0) {
consumed[1] = consumeY
this.scrollBy(0, consumeY)
}
改变mScrolly导致的,但是不知道如何下手修改,希望作者可以指点下。

1608629621475540.mp4

ChildRecyclerView 滑动问题

ParentRecyclerView 中有这么一段代码 当ChildRecyclerView 中item 是不规则的时候 computeVerticalScrollOffset()获取的滚动高度不准确

override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) { if (target is ChildRecyclerView) { // 下面这一坨代码的主要目的是计算consumeY var consumeY = dy val childScrollY = target.computeVerticalScrollOffset() } ........... }

冲突

加载更多会有滑动冲突

Fragment 中的 RecyclerView

Fragment 中的 RecyclerView 实现瀑布流feed 和 线性item 混排 有什么好的实现方式吗
目前 阿里的vlayout 能实现 就是滑动不是流畅 有卡顿

ChildRecyclerView没法触发下拉刷新

当使用这个版本的下拉刷新时
implementation 'com.scwang.smart:refresh-layout-kernel:2.0.3'
implementation 'com.scwang.smart:refresh-header-classics:2.0.3'

ChildRecyclerView没法触发下拉刷新

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.