Android NestedScrollingスライド衝突問題解決(3)-プロジェクト実戦

41525 ワード

実際のニーズ
前の2つの論文ではNestedScrollに関するインタフェースと一般的な処理ロジックを理解した.本論文では,具体的な連合スライド要件を実現する.
AndroidではレイアウトにWebViewを埋め込んでWebコンテンツを表示することが多く、WebViewの内部にはインタラクティブロジック(スクロールなど)があり、外部レイアウトでもスクロールロジックを処理するとスライド衝突があり、このようなシーンは実際のプロジェクト開発でよく見られます.例えばAppBarLayoutを含むCoordinatorLayoutにWebViewを埋め込むなど、WebViewの底にもう1つのfooterを置いてコレクションボタンを置くなど、上へスライドするときはまずWebViewをAppBarLayoutに従ってスライドさせ、AppBarLayoutで画面をスライドさせた後、WebViewは全画面で展示され、WebViewをスライドさせ続け、WebViewは最後まで漕いだ後、WebViewとfooterを一緒に上へスライドさせ続けます.実際の効果は次の図のようになります.
需要解析
このニーズに対して、CoordinatorLayoutおよびAppBarLayoutの理解によれば、WebViewをCoordinatorLayoutのサブlayoutに配置し、このlayoutのlayout_behaviorappbar_scrolling_view_behaviorに設定することで、スライド時にAppBarLayoutの底部にWebViewを維持し、AppBarLayoutが頂部WebViewの全画面表示にスライドするまでフォローすることができる.
しかし、WebViewの全画面表示後、WebViewの内容をスライドできないまでスライドし続け、footerをドラッグするにはどうすればいいのでしょうか.
1つの簡単な方法は、WebViewとfooterをカスタムlayoutに配置し、プログラミングはWebViewのコンテンツスクロールとレイアウト全体のスクロール(WebViewが最後までスクロールした後にレイアウトをスクロールする)を実現することです.layoutファイルは次のとおりです.

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:clipChildren="false"
    android:background="#ffffff"
    android:fitsSystemWindows="true">

    <android.support.design.widget.CoordinatorLayout
        android:id="@+id/preview_coordinator_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipChildren="false"
        android:fitsSystemWindows="true">

        <android.support.design.widget.AppBarLayout
            android:id="@+id/preview_app_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:clipChildren="false"
            app:elevation="0dp">

            <RelativeLayout
                android:id="@+id/title_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="@color/colorPrimary"
                app:layout_scrollFlags="scroll">

                <TextView
                    android:id="@+id/text_title"
                    android:layout_width="wrap_content"
                    android:layout_height="50dp"
                    android:layout_alignParentTop="true"
                    android:gravity="center"
                    android:textColor="#ffffff"
                    android:textStyle="bold"
                    android:textSize="18sp"
                    android:text="      "
                    android:layout_centerHorizontal="true"/>
            RelativeLayout>
        android.support.design.widget.AppBarLayout>

        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <com.lwons.nestedscrollexample.ScrollingContent
                android:id="@+id/scrolling_content"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#ffffff"
                android:orientation="vertical">
            com.lwons.nestedscrollexample.ScrollingContent>

        FrameLayout>

    android.support.design.widget.CoordinatorLayout>
LinearLayout>

ここで、com.lwons.nestedscrollexample.ScrollingContentは、LinearLayoutに基づくカスタムレイアウトである.中にはheightがMATCH_PARENTのWebViewとheightが実際の高さのfooterが置かれています.
WebViewおよびfooterのクリックイベントに影響を与えないためには、スライド関連のイベントの処理のみをブロックする必要があります.ここでは、カスタムレイアウトのonInterceptTouchEventMotionEventをフィルタする必要があります.次のようになります.
private int mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
private float mLastY;
private boolean mIsDraging;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    final int action = MotionEventCompat.getActionMasked(ev);

    if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
        mIsDraging = false;
        return false;
    }

    switch (action) {
        case MotionEvent.ACTION_MOVE: {
            if (mIsDraging) {
                return true;
            }
            final float yoff = Math.abs(mLastY - ev.getRawY());

            if (yoff > mTouchSlop) {
                //              ,      
                // Start scrolling!
                getParent().requestDisallowInterceptTouchEvent(true);
                return true;
            }
            break;
        }
        case MotionEvent.ACTION_DOWN:
            mLastY = ev.getRawY();
            break;
    }
    return false;
}

これにより、カスタムレイアウトはスライドイベントをブロックし、各ステップのスライド距離を得ることができますが、このスライド距離をどのように処理しますか.
NestedScrollインタフェースの使用方法と特徴を振り返ると、カスタムレイアウトでスライドイベントをブロックした後、外部レイアウトと連動する必要があり、連動および制御連動を開始する側はNestedScrollingChild(後にNCと略称する)であるため、カスタムレイアウトでNestedScrollingChild関連インタフェースを実現し、スライドロジックを制御する必要がある.NestedScrollingParent(後にNPと略称する)はどのレイアウトなのか、CoordinatorLayoutのコードからNPがCoordinatorLayoutであり、AppBarLayoutのスライドを処理することがわかります.
必要なスライドインタラクションの詳細については、スライドアップするときに、まずAppBarLayoutをスライドさせ、WebViewをスライドさせる必要があります.この部分はCoordinatorLayoutが実現します.dispatchNestedPreScrollを呼び出してCoordinatorLayoutに通知するだけでいいです.その後、AppBarLayoutが上部から滑り出した後、WebViewをスクロールし続ける必要があります.この部分は、WebViewのscrollByインタフェースを呼び出すだけで、自分で処理する必要があります.WebViewがスライドできない場合は、カスタムレイアウト全体をスクロールする必要があります.ここでも簡単です.カスタムレイアウトのscrollByインタフェースを呼び出すと、WebViewとfooter全体がスクロールされます.
ここまで解析すると,上向きにスライドする操作過程全体が明らかになった.下向きのプロセスは上向きとほぼ同じです.
処理ロジックのコードは次のとおりです.
@Override
public boolean onTouchEvent(MotionEvent ev) {
    boolean returnValue = false;

    MotionEvent event = MotionEvent.obtain(ev);
    final int action = MotionEventCompat.getActionMasked(event);
    float eventY = event.getRawY();
    switch (action) {
        case MotionEvent.ACTION_MOVE:
            if (getScrollState() == SCROLL_STAT_SCROLLING) {
                stopScroll();
            }
            if (mVelocityTracker == null) {
                mVelocityTracker = VelocityTracker.obtain();
            }
            if (!mIsDraging) {
                mIsDraging = true;
                startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH);
            }
            //     
            int deltaY = (int) (mLastY - eventY);
            mLastY = eventY;
            //   NP     ,  CoordinatorLayout   AppBarLayout   ScrollingContent  
            if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset, ViewCompat.TYPE_TOUCH)) {
                deltaY -= mScrollConsumed[1]; // mScrollConsumed[1] CoordinatorLayout      
                event.offsetLocation(0, -mScrollOffset[1]);
            }
            mVelocityTracker.addMovement(event);

            //              
            int scrollInternalY = 0;
            if (deltaY != 0) {
                scrollInternalY = scrollY(deltaY);
                deltaY -= scrollInternalY;
            }

            //              ,  NP    (NP           )
            if (deltaY != 0) {
                dispatchNestedScroll(0, mScrollConsumed[1]+scrollInternalY, 0, deltaY, mScrollOffset, ViewCompat.TYPE_TOUCH);
            }
            returnValue = true;
            break;
        case MotionEvent.ACTION_DOWN:
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            returnValue = true;
            mVelocityTracker.computeCurrentVelocity(1000);
            // fling  
            onFlingY((int) -mVelocityTracker.getYVelocity());
            mVelocityTracker.clear();
            mIsDraging = false;
            //          
            stopNestedScroll(ViewCompat.TYPE_TOUCH);
            break;
    }
    return returnValue;
}

/**
 *       
 * @param deltaY           
 * @return             
 */
private int scrollY(int deltaY) {
    int remainY = deltaY;
    int consumedY = 0;
    if (remainY > 0) {
        //     

        if (mWebview != null && mWebview.canScrollUp() > 0) {
            // WebView        
            int readerScroll = Math.min(mWebview.canScrollUp(), remainY);
            mWebview.scrollBy(0, readerScroll);
            remainY -= readerScroll;
            consumedY += readerScroll;
        }

        if (remainY > 0 && getScrollY() < mFooter.getHeight()) {
            //             
            int layoutScroll = Math.min(mFooter.getHeight() - getScrollY(), remainY);
            scrollBy(0, layoutScroll);
            consumedY += layoutScroll;
        }
    } else {
        //     

        if (getScrollY() > 0) {
            //             
            int layoutScroll = Math.max(-getScrollY(), remainY);
            scrollBy(0, layoutScroll);
            remainY -= layoutScroll;
            consumedY += layoutScroll;
        }

        if (mWebview != null && mWebview.canScrollDown() > 0) {
            // WebView        
            int readerScroll = Math.max(-mWebview.canScrollDown(), remainY);
            mWebview.scrollBy(0, readerScroll);
            consumedY += readerScroll;
        }
    }

    return consumedY;
}

完全な実装
このニーズに対応して、完全なAndroidエンジニアリングがGitHub上に配置されています.サンプルエンジニアリングGitHubアドレス
直接apkをダウンロードして効果を表示できます:サンプルapkダウンロード