The previous article [Usage] WebView realizes nested sliding, achieves a ceiling effect like silky, and is perfectly compatible with X5 webview. It
has already explained how to realize nested sliding. In this article, let us look at its realization principle. Without further ado, let's get into the text.
foreword
Before explaining, let's briefly talk about some concepts of nested sliding. (Buddies who are familiar with this can skip this directly)
When it comes to nested sliding, everyone should be familiar with it. He is the NestedScroll mechanism launched by Google after 5.0.
Maybe beginners will have such questions? Compared with the traditional event distribution mechanism, what are the advantages of the NetstedScroll mechanism.
In the traditional event distribution mechanism, once a View or ViewGroup consumes an event, it is difficult to pass the event to the parent View for joint processing. The NestedScrolling mechanism helps us solve this problem very well. We only need to implement the corresponding interface according to the specification. The child View implements NestedScrollingChild, the parent View implements NestedScrollingParent, and completes the interaction through NestedScrollingChildHelper or NestedScrollingParentHelper.
If you don't know about the NestedScrolling mechanism, you can read this article I wrote a few years ago.
In-depth analysis of NestedScrolling mechanism
He can achieve many cool effects in combination with CoordinatorLayout, such as the ceiling effect.
If you are interested, you can read these articles.
Use CoordinatorLayout to create various cool effects
Custom Behavior —— Like Zhihu, FloatActionButton hides and displays
In-depth analysis of NestedScrolling mechanism
Take you step by step to understand the source code of CoordinatorLayout
Custom Behavior - the realization of imitation Sina Weibo discovery page
ViewPager, ScrollView nested ViewPager sliding conflict resolution
Custom behavior - perfect imitation QQ browser homepage, meituan business details page
Principle realization
Not much nonsense, today, let's take a look at how WebView realizes nested sliding.
Principle brief
We know that there are currently several interfaces for nested sliding, NestedScrollingChild and NestedScrollingParent.
For an ACTION_MOVE action
- Before the scrolling child slides, it will use NestedScrollingChildHelper to find out whether there is a responsive scrolling parent. If so, it will first ask the scrolling parent whether it needs to slide before the scrolling child. If necessary, the scrolling parent will slide accordingly and consume a certain amount distance;
- Then the scrolling child slides accordingly, and consumes a certain distance value dx.
After the dy scrolling child slides, it asks the scrolling parent whether it needs to continue to slide, and if necessary, perform corresponding processing. - After the sliding is over, the Scrolling child will stop sliding, and notify the corresponding Scrolling Parent to stop sliding through NestedScrollingChildHelper.
- When the finger is lifted (Action_up), according to the sliding speed, calculate whether the fling is corresponding
And if our WebView wants to realize nested sliding, it can use this mechanism.
accomplish
The first step is to implement the NestedScroolChild3 interface and rewrite the corresponding method
public class NestedWebView extends WebView implements NestedScrollingChild3 {
public NestedWebView(Context context) {
this(context, null);
}
public NestedWebView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.webViewStyle);
}
public NestedWebView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOverScrollMode(WebView.OVER_SCROLL_NEVER);
initScrollView();
mChildHelper = new NestedScrollingChildHelper(this);
setNestedScrollingEnabled(true);
}
// 省略
}
Step two:
- At the time
ACTION_DOWN
of , first call the startNestedScroll method to tell NestedScrollParent that I am going to slide - Then,
ACTION_MOVE
at the time, calldispatchNestedPreScroll
the method, let NestedScrollParent have the opportunity to slide in advance, and then call its owndispatchNestedScroll
method to carry out the activity
public boolean onTouchEvent(MotionEvent ev) {
initVelocityTrackerIfNotExists();
MotionEvent vtev = MotionEvent.obtain(ev);
final int actionMasked = ev.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
mNestedYOffset = 0;
}
vtev.offsetLocation(0, mNestedYOffset);
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
if ((mIsBeingDragged = !mScroller.isFinished())) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
if (!mScroller.isFinished()) {
abortAnimatedScroll();
}
mLastMotionY = (int) ev.getY();
mActivePointerId = ev.getPointerId(0);
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
break;
case MotionEvent.ACTION_MOVE:
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
if (activePointerIndex == -1) {
Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent");
break;
}
final int y = (int) ev.getY(activePointerIndex);
int deltaY = mLastMotionY - y;
if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset,
ViewCompat.TYPE_TOUCH)) {
deltaY -= mScrollConsumed[1];
mNestedYOffset += mScrollOffset[1];
}
if (!mIsBeingDragged && Math.abs(deltaY) > mTouchSlop) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
mIsBeingDragged = true;
if (deltaY > 0) {
deltaY -= mTouchSlop;
} else {
deltaY += mTouchSlop;
}
}
if (mIsBeingDragged) {
mLastMotionY = y - mScrollOffset[1];
final int oldY = getScrollY();
final int range = getScrollRange();
// Calling overScrollByCompat will call onOverScrolled, which
// calls onScrollChanged if applicable.
if (overScrollByCompat(0, deltaY, 0, oldY, 0, range, 0,
0, true) && !hasNestedScrollingParent(ViewCompat.TYPE_TOUCH)) {
mVelocityTracker.clear();
}
final int scrolledDeltaY = getScrollY() - oldY;
final int unconsumedY = deltaY - scrolledDeltaY;
mScrollConsumed[1] = 0;
dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, mScrollOffset,
ViewCompat.TYPE_TOUCH, mScrollConsumed);
mLastMotionY -= mScrollOffset[1];
mNestedYOffset += mScrollOffset[1];
}
break;
Step 3: When ACTION_UP, calculate the sliding speed in the vertical direction and distribute it
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
if (!dispatchNestedPreFling(0, -initialVelocity)) {
dispatchNestedFling(0, -initialVelocity, true);
fling(-initialVelocity);
}
} else if (mScroller.springBack(getScrollX(), getScrollY(), 0, 0, 0,
getScrollRange())) {
ViewCompat.postInvalidateOnAnimation(this);
}
mActivePointerId = INVALID_POINTER;
endDrag();
break;
Also rewrite computeScroll
the method to handle inertial sliding
// 在更新 mScrollX 和 mScrollY 的时候会调用
public void computeScroll() {
if (mScroller.isFinished()) {
return;
}
mScroller.computeScrollOffset();
final int y = mScroller.getCurrY();
int unconsumed = y - mLastScrollerY;
mLastScrollerY = y;
// Nested Scrolling Pre Pass
mScrollConsumed[1] = 0;
dispatchNestedPreScroll(0, unconsumed, mScrollConsumed, null,
ViewCompat.TYPE_NON_TOUCH);
unconsumed -= mScrollConsumed[1];
if (unconsumed != 0) {
// Internal Scroll
final int oldScrollY = getScrollY();
overScrollByCompat(0, unconsumed, getScrollX(), oldScrollY, 0, getScrollRange(),
0, 0, false);
final int scrolledByMe = getScrollY() - oldScrollY;
unconsumed -= scrolledByMe;
// Nested Scrolling Post Pass
mScrollConsumed[1] = 0;
dispatchNestedScroll(0, 0, 0, unconsumed, mScrollOffset,
ViewCompat.TYPE_NON_TOUCH, mScrollConsumed);
unconsumed -= mScrollConsumed[1];
}
if (unconsumed != 0) {
abortAnimatedScroll();
}
// 判断是否滑动完成,没有完成的话,继续滑动 mScroller
if (!mScroller.isFinished()) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
Finally, in order to ensure onTouchEvent
that can receive touch events, we onInterceptTouchEvent
intercept in
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getAction();
if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { // most common
return true;
}
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_MOVE:
final int y = (int) ev.getY(pointerIndex);
final int yDiff = Math.abs(y - mLastMotionY);
// 判断一下滑动距离并且是竖直方向的滑动
if (yDiff > mTouchSlop
&& (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) {
// 代表药进行拦截
mIsBeingDragged = true;
mLastMotionY = y;
initVelocityTrackerIfNotExists();
mVelocityTracker.addMovement(ev);
mNestedYOffset = 0;
// 请求父类不要拦截事件
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
break;
case MotionEvent.ACTION_DOWN:
mLastMotionY = (int) ev.getY();
mActivePointerId = ev.getPointerId(0);
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
mScroller.computeScrollOffset();
mIsBeingDragged = !mScroller.isFinished();
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
break;
return mIsBeingDragged;
}
After processing, our webview implements the NestedScrol mechanism, and nested sliding can be performed.
[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-ZLoWwHcf-1663672759560)(https://raw.githubusercontent.com/gdutxiaoxu/blog_image/master/22/04 /webview%20%E5%B5%8C%E5%A5%97%E6%BB%91%E5%8A%A8.gif)]
X5 webView compatible
When I moved the code to x5 webview, I swipe at this time and found that it cannot be linked.
class NestedWebView extends com.tencent.smtt.sdk.WebView implements NestedScrollingChild3
Cause Analysis
What is the reason?
We clicked into the code in X5 webView and found that webView inherits FrameLayout, not the system WebView.
Therefore, we directly extend com.tencent.smtt.sdk.WebView to intercept touch events. In fact, we intercept FrameLayout instead of intercepting WebView inside, which will definitely not achieve nested sliding.
solution
Let's take a look at the View Tree structure of X5 webView first, because the code of X5 webView is confusing, and it is not convenient for us to directly see its View Tree through the code.
So, we can print out the x5 webView viewTree structure through code
webView = view.findViewById<WebView>(R.id.webview)
val childCount = webView.childCount
Log.i(TAG, "onViewCreated: webView is $webView, childCount is $childCount")
for (i in 0 until childCount) {
Log.i(TAG, "x5 webView: childView[$i] is ${webView.getChildAt(i)}")
}
Run the above code and get the following result
It can be seen that X5 WebView should wrap a layer of FrameLayout on the basis of WebView.
Then we have no way to get the TencentWebViewProxy$InnerWebView object inside, in fact, there are. He has getView
a .
After getting this object, do we have a way to intercept it, like onTouchEvent, onInterceptTouchEvent method?
We found such a description in the official documentation X5 webview FAQ
3.10 How to rewrite the screen events of TBS WebView (such as overScrollBy)
requires setWebViewCallbackClient and setWebViewClientExtension Reference code sample http://res.imtt.qq.com/tbs/BrowserActivity.zip
Through code tracking & debugging, we found the interface of WebViewCallBackClient
When the webview in X5 slides, the corresponding method will be called. Then, we can do the same at this time and move the code logic of the above NestedWebView down.
Rewrite onTouchEvent
, onInterceptTouchEvent
, computeScroll
these key methods.
This achieves nested sliding.
The specific code can be found in nestedwebview
Summarize
- With the help of the NestedScrool mechanism, it is actually quite simple to realize nested sliding. Basically, it is enough to change it according to the template code, and learn to infer other cases from one instance.
- If you want to achieve some custom effects, then we can achieve it through Behavior. For details, please refer to Custom behavior - perfect imitation QQ browser homepage, meituan business details page
reference blog
NestedWebView working properly with ScrollingViewBehavior
source address
nestedwebview , can help give a star.
If you think it is helpful to you, you can follow me on my WeChat public account Xu Gong , here is an Android advanced growth knowledge system, I hope we can learn and progress together, pay attention to the public account Xu Gong , and build core competitiveness together