Skip to content

QMUIContinuousNestedScrollLayout

cgspine edited this page Jun 10, 2019 · 3 revisions

QMUI版本要求: v1.3.2+

QMUIContinuousNestedScrollLayout,用于连接两个上下滚动的容器,使他们能够连续滚动。例如一个 WebView 用于显示网页内容, 一个 RecyclerView 用于显示评论。 在 QMUI 里, 把上面的滚动容器叫 TopView, 把下面的滚动容器叫 BottomView

使用

// 1. 提供 TopView
IQMUIContinuousNestedTopView topView = ...
// 2. 提供 BottomView
IQMUIContinuousNestedBottomView bottomView = ...
QMUIContinuousNestedScrollLayout scrollLayout = new QMUIContinuousNestedScrollLayout(context)

// 3. 将 TopView 赋值给 QMUIContinuousNestedScrollLayout
CoordinatorLayout.LayoutParams topLp = new CoordinatorLayout.LayoutParams(matchParent, matchParent);
topLp.setBehavior(new QMUIContinuousNestedTopAreaBehavior(context)); // not necessary
scrollLayout.setTopAreaView(topView, topLp);

// 4. 将 BottomView 赋值给 QMUIContinuousNestedScrollLayout
CoordinatorLayout.LayoutParams recyclerViewLp = new CoordinatorLayout.LayoutParams( matchParent, matchParent);
recyclerViewLp.setBehavior(new QMUIContinuousNestedBottomAreaBehavior());  // not necessary
scrollLayout.setBottomAreaView(bottomView, recyclerViewLp);

1. 提供 TopView

QMUI 默认实现了常用的三种 TopView

  1. QMUIContinuousNestedTopLinearLayout
  2. QMUIContinuousNestedTopRecyclerView
  3. QMUIContinuousNestedTopWebView

以及可以包装 Header 与 Footer 的容器类: QMUIContinuousNestedTopDelegateLayout

如果上述 TopView 不能满足需求,可以通过实现 IQMUIContinuousNestedTopView 实现自定义的 TopView

public interface IQMUIContinuousNestedTopView extends IQMUIContinuousNestedScrollCommon {
    // 消耗滚动,传入参数为可消耗量,返回值为未被消耗的值
    int consumeScroll(int dyUnconsumed);
    // 当前滚动位置
    int getCurrentScroll();
    // 可滚动范围
    int getScrollOffsetRange();
}


public interface IQMUIContinuousNestedScrollCommon {

    // 滚动状态, 与 RecyclerView 相同
    int SCROLL_STATE_IDLE = RecyclerView.SCROLL_STATE_IDLE;
    int SCROLL_STATE_DRAGGING = RecyclerView.SCROLL_STATE_DRAGGING;
    int SCROLL_STATE_SETTLING = RecyclerView.SCROLL_STATE_SETTLING;

    // 保存当前滚动信息
    void saveScrollInfo(@NonNull Bundle bundle);

    // 恢复滚动信息
    void restoreScrollInfo(@NonNull Bundle bundle);

    // 注入滚动监听器
    void injectScrollNotifier(OnScrollNotifier notifier);

    interface OnScrollNotifier {
        void notify(int innerOffset, int innerRange);

        void onScrollStateChange(View view, int newScrollState);
    }
}

2. 提供 BottomView

QMUI 只提供了 QMUIContinuousNestedBottomRecyclerView 以及可以包装 Header 的容器类: QMUIContinuousNestedBottomDelegateLayout

如果上述 BottomView 不能满足需求,可以通过实现 IQMUIContinuousNestedBottomView 实现自定义的 BottomView:

public interface IQMUIContinuousNestedBottomView extends IQMUIContinuousNestedScrollCommon {
    int HEIGHT_IS_ENOUGH_TO_SCROLL = -1;

    // 消耗滚动,传入参数为可消耗量,没有返回值,意味着 BottomView 必须处理所有的滚动
    // IQMUIContinuousNestedBottomView 需要是 NestedScrollingChild,才能方便的处理所有的滚动
    void consumeScroll(int dyUnconsumed);

    // 在 duration 的时间内滚动 dy
    void smoothScrollYBy(int dy, int duration);

    // 取消上面的smoothScroll
    void stopScroll();

    // 内容高度,如果 BottomView 内容高度足够滚动,则返回 HEIGHT_IS_ENOUGH_TO_SCROLL
    int getContentHeight();

    // 当前滚动位置
    int getCurrentScroll();

    // 滚动范围
    int getScrollOffsetRange();
}

要使得 QMUIContinuousNestedScrollLayout 可以正常工作,BottomView 最好实现 NestedScroll 机制。

3. 将 TopView/BottomView 赋值给 QMUIContinuousNestedScrollLayout

QMUIContinuousNestedScrollLayout 本质是 CoordinatorLayout, 因此 TopViewBottomViewLayoutParam 需要是 CoordinatorLayout.LayoutParam

QMUIContinuousNestedScrollLayout 重定义了 TopViewLayoutParam 的 height 值的含义:

  1. 如果 height == MATCH_PARENT, 则其高度最高为父容器高度。QMUIContinuousNestedTopRecyclerViewQMUIContinuousNestedTopWebViewQMUIContinuousNestedTopDelegateLayout 应该设置为它,当内容高度超出容器高度时,可以进行内部滚动。
  2. 如果 height != MATCH_PARENT, 则其高度等于它内容的高度。QMUIContinuousNestedTopLinearLayout 应当设置为它(wrap_content),因为 LinearLayout 内部无法滚动,只能靠外部去驱动它滚动。

scroll 支持

触发 scroll

QMUIContinuousNestedScrollLayout 支持以下scroll 行为:

方法 描述
scrollBy(int dy) 滚动 dy 的距离,正值向上滚,负值向下滚动
smoothScrollBy(int dy, int duration) 在给定时间滚动dy的距离
scrollToTop() 滚动到顶部
scrollToBottom() 滚动到底部
scrollBottomViewToTop() 将 BottomView 滚动到顶部

scroll 监听

可以通过 QMUIContinuousNestedScrollLayout.addOnScrollListener 添加监听器, 监听器需要实现 OnScrollListener 接口:

public interface OnScrollListener {

    // topCurrent、topRange 是指 TopView 的内部滚动值与滚动范围
    // bottomCurrent、bottomRange 是指 BottomView 的内部滚动值与滚动范围
    // offsetCurrent、offsetRange 是指 TopView 与 BottomView 自身的滚动偏移值和整体可偏移值
    void onScroll(int topCurrent, int topRange,
                  int offsetCurrent, int offsetRange,
                  int bottomCurrent, int bottomRange);

    // 滚动状态:
    // fromTopBehavior 标识有 TopBehavior 触发的滚动状态改变,否则为 BottomView 内部触发滚动状态变化。
    void onScrollStateChange(int newScrollState, boolean fromTopBehavior);
}

滚动位置恢复与还原

对于很多用户友好型的页面,最好能够在再次进入同一个页面时,能够恢复到上次阅读位置。 因此 QMUIContinuousNestedScrollLayout 提供了对当前位置信息的恢复与还原机制。

方法 描述
saveScrollInfo(Bundle bundle) 将当前滚动位置存储到 Bundle 里
restoreScrollInfo(Bundle bundle) 从 Bundle 里恢复滚动位置

如果是自定义的 TopView / BottomView,可以通过重写 saveScrollInfo(@NonNull Bundle bundle)void restoreScrollInfo(@NonNull Bundle bundle) 来做完成滚动位置的恢复工作。