QMUI in v1.3.2 provides an entirely new components: QMUIContinuousNestedLayout. Click here to view usage documentation. This article will talk about its use scenario, design and implementation.

Many apps use a WebView to display content and a list at the bottom to display comments. This is QMUIContinuousNestedLayout a usage scenario. But support the use of more QMUIContinuousNestedLayout scene:

The origin of

Component creation cannot be separated from requirement scenarios, and the design of components varies greatly according to different requirement scenarios. QMUIContinuousNestedLayout is produced by WeChat reading story flow, it provide the function also is completely in order to satisfy the story flow interface for details. Compared with the details page of general information flow, the details interface of Wechat reading story stream is more complex: Need to support at the same time WebView/RecyclerView/custom typeset View/ordinary LinearLayout View and nested RecyclerView ViewPager connection.

NestedScrollmechanism

For the implementation of NestedScroll components, the best choice must be the official NestedScroll mechanism, which can further choose the CoordinatorLayout that implements this mechanism. But although QMUIContinuousNestedLayout inherited CoordinatorLayout, but not completely follow NestedScroll mechanism. Why is that? So let’s look at the NestedScroll mechanism.

The NestedScroll mechanism was introduced after Android L. Before that, the only way to handle scroll was to rely on external and internal interceptors.

  • External interception: External container passesonInterceptTouchEventIntercepting the passing of the event, the external container detects and handles the scroll.
  • Internal interception: Internal containerrequestDisallowInterceptTouchEventRequires the system to pass events directly to the internal container.

Generally speaking, external and internal interception methods cannot be shared. Otherwise may not have the opportunity to call requestDisallowInterceptTouchEvent inside container.

The NestedScroll mechanism uses internal interception. So events are always passed to the inner view first. The handling of the event is then constrained by NestedScrollingChild and NestedScrollingParent. There are more interfaces, so I won’t list them here. The most important thing is to understand its processing logic: the innermost NestedScrollingChild gets the event and calculates the scroll amount. The scroll amount can be processed in three steps as follows:

  1. askNestedScrollingParentDo you want to consume scrolling? How much is consumed? (onNestedPreScroll).
  2. If the rolling amount is not completely consumed, then judgeNestedScrollingChildShould I consume scrolling? How much is consumed? (Component internal implementation).
  3. If scrolling is not used up, ask againNestedScrollingParentDo you want to consume the remaining scrolling? (onNestedScroll).

Generally speaking, our inner View is RecyclerView, which has realized NestedScrollingChild. We only need the outer container to realize NestedScrollingParent to judge whether it needs to consume mixing momentum. But if the inner View is a custom View, then we need to implement the NestedScrollingChild ourselves, which is relatively complex. So I didn’t fully adopt NestedScroll mechanism, which requires WebView, LinearLayout and custom typesetting View to implement NestedScrollingChild. The former two are ok. However, the event distribution logic of our typesetting View is highly customized and difficult to access, so I used external interception for TopView but handled some callback points of NestedScroll mechanism.

Event Distribution Process

QMUIContinuousNestedLayout can set two roll containers, withmicrosoft and BottomView respectively. Having only two scroll containers is sufficient for now, and will be sufficient for future extensions. Later period can be extended QMUIContinuousNestedLayout as withmicrosoft to support or to another QMUIContinuousNestedLayout BottomView nested.)

  • TopViewGenerally is a variety of, so the use of external interception method, rolling amount calculated by the outer layer, the specific consumption behavior byTopViewImplementation is actually made up ofQMUIContinuousNestedTopAreaBehaviorIntercept.
  • BottomViewThe inner layer of theRecyclerView, thus directly adoptedNestedScrollMechanism. (It’s 2019. Forget itListView!)

Rolling consumption can be divided into three parts:

  1. TopViewInternal consumption
  2. BottomViewInternal consumption
  3. TopViewwithBottomViewIs called “offset consumption”.

The overall process of event distribution can be divided into two types:

  1. If the Down event occurs inTopViewOn:

    A. byQMUIContinuousNestedTopAreaBehaviorIntercept the event and count the scroll.

    B. If the scroll is up, proceed firstTopViewInternal consumption, and then offset consumption. If scrolling down, the offset is consumed first, then proceedTopViewInternal consumption. (Because the layout is accurate, it doesn’t exist hereBottomViewInternal consumption)

    C. When the Up event occurs, the fling is triggered. If the event is an upward scroll, the fling is triggeredBottomViewInternal consumption.
  2. If the Down event occurs inBottomViewOn:

    A. The amount of rolling is made up of the innermost layersNestedScrollingChildIt produces, and then it interacts with the outerQMUIContinuousNestedScrollLayout(CoordinatorLayout) for rolling consumption.

    b. QMUIContinuousNestedScrollLayoutAnd delegate the consumption behavior toQMUIContinuousNestedTopAreaBehavior.

    In c.QMUIContinuousNestedTopAreaBehaviorIf you scroll up, thenonNestedPreScrollPriority to determine whether offset consumption is required; If you’re scrolling down, you need to scroll downonNestedScrollIs used for offset consumption according to the remaining roll quantity.

    D. When the Up event occurs, the fling is triggeredTopViewInternal consumption.

The main logic is sorted out here to let readers know when to execute what code, the specific code is not posted, you can go to Github to view the source code.

Interface design

Now that we know the overall flow, let’s look at the interface design between TopView and BottomView.

There are only three main TopView interfaces:

Public interface IQMUIContinuousNestedTopView extends IQMUIContinuousNestedScrollCommon {/ / incoming unused roll, The return value should be the amount of 'TopView' that has not been consumed after processing. // integer. MAX_VALUE: scroll to the bottom // integer. MIN_VALUE: scroll to the top int consumeScroll(int dyUnconsumed); Int getCurrentScroll(); Int getScrollOffsetRange(); }Copy the code

BottomView’s interface is relatively more, the main reason is that all behavior is QMUIContinuousNestedTopAreaBehavior withmicrosoft to intercept and handle, so it don’t need to deal with smoothScroll, etc.

public interface IQMUIContinuousNestedBottomView extends IQMUIContinuousNestedScrollCommon { int HEIGHT_IS_ENOUGH_TO_SCROLL = -1; // Pass in the unconsumed scroll. Since NestedScroll is used, there is no need to relate the unconsumed scroll after processing. // integer. MAX_VALUE: scroll to the bottom // integer. MIN_VALUE: scroll to the top void consumeScroll(int dyUnconsumed); Void smoothScrollYBy(int dy, int duration); void stopScroll(); /** * The height of BottomView does not necessarily hold the entire content area. If you do nothing, there will be a lot of space when you scroll completely to BottomView, so you add this interface. Otherwise return HEIGHT_IS_ENOUGH_TO_SCROLL */ int getContentHeight(); int getCurrentScroll(); int getScrollOffsetRange(); }Copy the code

Here getScrollOffsetRange puteVerticalScrollRange () and View.com () is not consistent, computeVerticalScrollRange () is returned to the real length of content, And getScrollOffsetRange () returns the maximum rolling, generally equal to computeVerticalScrollRange () – getHeight ().

TopView and BottomView have special definitions for integer. MAX_VALUE and integer. MIN_VALUE, which are particularly friendly in implementations such as RecyclerView. This can be done quickly with scrollToPosition.

Tips: the WebView getContentHeight () is not allowed to, but computeVerticalScrollRange () is very accurate, WebView scroll bar is also dependent on it, so can trust. But getScrollY sometimes is not accurate, even more than computeVerticalScrollRange (), so the calculation for rolling position and quantity of the rolling should be combined with computeVerticalScrollRange () to do the most value to protect.

other

QMUIContinuousNestedTopDelegateLayout withmicrosoft to add the Header/Footer. QMUIContinuousNestedBottomDelegateLayout for BottomView added Sticky Header. Less QMUIContinuousNestedBottomDelegateLayout didn’t add a Footer, because scene, and can be used as a RecyclerView itemView.

In terms of implementation, it mainly relies on QMUIViewOffsetHelper to handle the scrolling position. There is also the ViewOffsetHelper utility class officially, but it is not public. It is a very useful utility class, which is very useful in scrolling, position offset and other scenarios. If you’re interested, sometimes you can look at the implementation of the official component and learn a lot of useful coding tricks.

QMUIContinuousNestedScrollLayout also provides scrolling position information of the save and restore function, its implementation and the View state storage and recovery, with a Key – Value in the form of collecting into a Bundle. Of course, there are drawbacks: if two views have the same ID, state recovery will fail; If the key value conflict, then QMUIContinuousNestedScrollLayout restore will not accurate. Because QMUIContinuousNestedScrollLayout now can not do with DelegateLayout multi-level nested (should not someone do so)

The last feature is the implementation of scrolllistener:

public interface OnScrollListener {

    void onScroll(int topCurrent, int topRange,
                  int offsetCurrent, int offsetRange,
                  int bottomCurrent, int bottomRange);

    void onScrollStateChange(int newScrollState, boolean fromTopBehavior);
}
Copy the code

It will provide the user with six nibbles, including the current value and range value of TopView, BottomView and offset, which can be flexibly used by the user. Of course, the onScroll callback may be slightly more than that of a normal scroll container, because both containers and the external offset are triggered and may be repeated, so it is best not to do time-consuming operations.

conclusion

It may be easy to write a Demo of a complex UI component, but it is not so easy to flexibly coordinate the use of various scenarios. At this time, a good design is quite important. At present, this component has experienced the continuous polishing of wechat book chapters, comic chapters, books and public accounts. It can only be said that it can meet the current needs, but who knows what requirements the current component cannot meet? Product or design whimsy tends to be reused with a little differentiation, and then the whole thing pops up. So, read the source code, although duplicate wheel is not recommended, but at the UI level, it is unavoidable, at least to change the wheel.