最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

WebView 解決嵌套滑動沖突,絲滑般實(shí)現(xiàn)吸頂效果,完美兼容 X5 webview

2023-03-14 22:33 作者:程序員徐公  | 我要投稿


上一篇文章?【使用篇】WebView 實(shí)現(xiàn)嵌套滑動,絲滑般實(shí)現(xiàn)吸頂效果,完美兼容 X5 webview
已經(jīng)講解了如何實(shí)現(xiàn)嵌套滑動,這篇文章,讓我們一起來看他的實(shí)現(xiàn)原理。廢話不多說,開始進(jìn)入正文。

前言

講解之前,先簡單說一下嵌套滑動的一些概念。(熟悉這個的哥們可以直接跳過這個)

說到嵌套滑動,大家應(yīng)該都不陌生。他是 Google 在 5.0 之后推出來的 NestedScroll 機(jī)制。

可能初學(xué)者會有這樣的疑問?想比較于傳統(tǒng)的事件分發(fā)機(jī)制,NetstedScroll 機(jī)制有什么優(yōu)點(diǎn)。

在傳統(tǒng)的事件分發(fā)機(jī)制 中,一旦某個 View 或者 ViewGroup 消費(fèi)了事件,就很難將事件交給父 View 進(jìn)行共同處理。而 NestedScrolling 機(jī)制很好地幫助我們解決了這一問題。我們只需要按照規(guī)范實(shí)現(xiàn)相應(yīng)的接口即可,子 View 實(shí)現(xiàn) NestedScrollingChild,父 View 實(shí)現(xiàn) NestedScrollingParent ,通過 NestedScrollingChildHelper 或者 NestedScrollingParentHelper 完成交互。

如果對于 NestedScrolling 機(jī)制不了解的,可以看我?guī)啄昵皩懙倪@篇文章。
NestedScrolling 機(jī)制深入解析

他結(jié)合 CoordinatorLayout 可以實(shí)現(xiàn)很多炫酷的效果,比如吸頂效果等。

有興趣的話可以看這些文章。

使用CoordinatorLayout打造各種炫酷的效果

自定義Behavior —— 仿知乎,F(xiàn)loatActionButton隱藏與展示

NestedScrolling 機(jī)制深入解析

一步步帶你讀懂 CoordinatorLayout 源碼

自定義 Behavior -仿新浪微博發(fā)現(xiàn)頁的實(shí)現(xiàn)

ViewPager,ScrollView 嵌套ViewPager滑動沖突解決

自定義 behavior - 完美仿 QQ 瀏覽器首頁,美團(tuán)商家詳情頁

原理實(shí)現(xiàn)

廢話不多說,今天,讓我們一起來看看 WebView 怎樣實(shí)現(xiàn)嵌套滑動。

請?zhí)砑訄D片描述

原理簡述

我們知道,嵌套滑動目前主要有幾個接口 NestedScrollingChild,NestedScrollingParent 。

對于一個 ACTION_MOVE 動作

  • scrolling child 在滑動之前,會通過 NestedScrollingChildHelper 查找是否有響應(yīng)的 scrolling parent,如果有的話,會先詢問scrolling parent 是否需要先于scrolling child 滑動,如果需要的話,scrolling parent 進(jìn)行相應(yīng)的滑動,并消費(fèi)一定的距離;

  • 接著scrolling child 進(jìn)行相應(yīng)的滑動,并消耗一定的距離值 dx,dy
    scrolling child 滑動完之后,詢問scrolling parent 是否還需要繼續(xù)進(jìn)行滑動,需要的話,進(jìn)行相應(yīng)的處理。

  • 滑動結(jié)束之后,Scrolling child 會停止滑動,并通過 NestedScrollingChildHelper 通知相應(yīng)的 Scrolling Parent 停止滑動。

  • 手指抬起的時候(Action_up) 的時候,根據(jù)滑動速度,計(jì)算是否相應(yīng) fling

而我們的 WebView 如果要實(shí)現(xiàn)嵌套滑動,那就可以借助這套機(jī)制。

實(shí)現(xiàn)

第一步,實(shí)現(xiàn) NestedScroolChild3 接口,并重寫相應(yīng)的方法

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);
????}

????//?省略
}

第二步:

  • 在?ACTION_DOWN?的時候,先調(diào)用 startNestedScroll 方法,告訴 NestedScrollParent,說我要滑動了

  • 接著,在?ACTION_MOVE?的時候,調(diào)用?dispatchNestedPreScroll?方法,讓 NestedScrollParent 有機(jī)會可以提前滑動,接著調(diào)用自身的?dispatchNestedScroll?方法,進(jìn)行活動

???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;

第三步:在 ACTION_UP 的時候,計(jì)算一下垂直方向的滑動速度,并進(jìn)行分發(fā)

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;

同時重寫?computeScroll?方法,處理慣性滑動

//?在更新?mScrollX?和?mScrollY?的時候會調(diào)用
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();
????}

????//?判斷是否滑動完成,沒有完成的話,繼續(xù)滑動?mScroller
????if?(!mScroller.isFinished())?{
????????ViewCompat.postInvalidateOnAnimation(this);
????}
}

最后,為了確保?onTouchEvent?能夠收到觸摸事件,我們在?onInterceptTouchEvent?中進(jìn)行攔截

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)?{
????????????????//?代表藥進(jìn)行攔截
????????????????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;
}

處理完之后,我們的 webview 就實(shí)現(xiàn)了 NestedScrol 機(jī)制,可以進(jìn)行嵌套滑動了。

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(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 兼容

當(dāng)我將代碼搬到 x5 webview 的時候,這時候進(jìn)行滑動,發(fā)現(xiàn)無法聯(lián)動了。

class?NestedWebView?extends?com.tencent.smtt.sdk.WebView?implements?NestedScrollingChild3

原因分析

這是什么原因呢?

我們點(diǎn)進(jìn)去 X5 webView 里面的代碼,發(fā)現(xiàn) webView 是繼承 FrameLayout,而不是繼承系統(tǒng) WebView。

因此我們直接 extends?com.tencent.smtt.sdk.WebView,對觸摸事件進(jìn)行攔截,實(shí)際上是對 FrameLayout 進(jìn)行攔截處理,而不是對里面的 WebView 進(jìn)行攔截處理,那肯定達(dá)不到嵌套滑動。

解決方案

我們先來看一下 X5 webView 的 View Tree 結(jié)構(gòu),因?yàn)?X5 webView 代碼是混淆的,我們想要通過代碼直接看出他的 View Tree,是不太方便的。

于是,我們可以通過代碼,將 x5 webView viewTree 結(jié)構(gòu)打印出來

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)}")
}

運(yùn)行以上代碼,得到以下結(jié)果

可以看到 X5 WebView 應(yīng)該就是在 WebView 的基礎(chǔ)之上包了一層 FrameLayout。

那我們對沒有辦法拿到里面的 TencentWebViewProxy$InnerWebView 對象,其實(shí)是有的。他在里面有一個?getView?的方法。

拿到這個對象之后,我們有辦法進(jìn)行攔截處理嘛,像 onTouchEvent, onInterceptTouchEvent 方法?

我們在官方文檔中?X5 webview 常見問題?找到這樣的描述

3.10 如何重寫TBS WebView 的屏幕事件(例如 overScrollBy)
需 setWebViewCallbackClient 和 setWebViewClientExtension 參考代碼示例 http://res.imtt.qq.com/tbs/BrowserActivity.zip

通過代碼跟蹤&調(diào)試,我們發(fā)現(xiàn)了 WebViewCallBackClient 的接口

當(dāng) X5 里面的 webview 進(jìn)行滑動的時候,會調(diào)用相應(yīng)的方法。那么,我們這時候就可以依樣畫葫蘆,將上面 NestedWebView 的代碼邏輯搬下來。

重寫?onTouchEvent,?onInterceptTouchEvent,?computeScroll?這幾個關(guān)鍵方法。

這樣就實(shí)現(xiàn)了嵌套滑動。

具體的代碼可以見?nestedwebview

總結(jié)

  1. 借助 NestedScrool 機(jī)制,要實(shí)現(xiàn)嵌套滑動其實(shí)還是蠻簡單的,基本按照模板代碼魔改一下就好了,要學(xué)會舉一反三。

  2. 如果要實(shí)現(xiàn)一些自定義的效果,那么我們可以通過 Behavior 來實(shí)現(xiàn),具體的可以參照?自定義 behavior - 完美仿 QQ 瀏覽器首頁,美團(tuán)商家詳情頁


WebView 解決嵌套滑動沖突,絲滑般實(shí)現(xiàn)吸頂效果,完美兼容 X5 webview的評論 (共 條)

分享到微博請遵守國家法律
从江县| 上犹县| 甘南县| 夏河县| 万盛区| 论坛| 甘德县| 扎鲁特旗| 修水县| 运城市| 肃南| 遂宁市| 龙泉市| 乌审旗| 丹东市| 宝清县| 略阳县| 高青县| 孟连| 寻乌县| 原阳县| 扶绥县| 乾安县| 中阳县| 盐池县| 克东县| 满洲里市| 苏尼特右旗| 灵川县| 景宁| 诸暨市| 达州市| 嘉荫县| 阳春市| 黄陵县| 读书| 郴州市| 红原县| 札达县| 古浪县| 济源市|