• Stars
    star
    308
  • Rank 135,712 (Top 3 %)
  • Language
    Kotlin
  • License
    Apache License 2.0
  • Created over 4 years ago
  • Updated almost 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

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

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下载

点击下载

More Repositories

1

ViewpagerTransition

viewpager with parallax pages, together with vertical sliding (or click) and activity transition
Java
3,039
star
2

CardSlidePanel

enable users to slide card to the left or right smoothly and continuously
Java
2,385
star
3

AndroidPileLayout

An abnormal horizontal ListView-like pile layout with stretch and contraction effects.
Java
2,157
star
4

VegaLayoutManager

√A customized LayoutManager - fade and shrink the head itemView when scrolling.
Java
1,859
star
5

VerticalSlideFragment

vertical slide to switch to the next fragment page, looks like vertical viewpager
Java
1,606
star
6

DragRankSquare

edit personal information which enables users to drag and rank image order
Java
1,118
star
7

AnimRichEditor

rich text editor which enables users to insert/delete bitmaps and text into edit-view with animations.
Java
976
star
8

android-snake-menu

imitate Tumblr's menu, dragging animations look like a snake
Java
584
star
9

stickyViewpager

[deprecated] sticky view in viewpager which includes scrollview and listview - viewpager with headers
Java
313
star
10

PersistentCoordinatorLayout

京东首页 - 长列表内嵌ViewPager商品流(CoordinatorLayout方案)
Kotlin
212
star
11

android-character-animation

in textview, characters come along one by one with random alpha animation
Java
59
star
12

android-slide-to-unlock

imitate ios, slide to unlock, text color gradient animation
Java
47
star
13

AutoWrapLayout

a customized viewgroup wraps textviews and aligns on both left and right sides.
Java
46
star
14

boringArticles

just some articles
19
star
15

MailExtension

chrome插件 - 工资条助手
JavaScript
14
star