web-dev-qa-db-ja.com

setNestedScrollingEnabled(false)を使用するときにスクロール自体のブロックを回避するにはどうすればよいですか?

バックグラウンド

CollapsingToolbarLayoutがあり、下部にRecyclerViewがある非常に複雑なレイアウトがあります。

場合によっては、RecyclerViewでsetNestedScrollingEnabled(boolean)を呼び出して、CollapsingToolbarLayoutの展開/折りたたみを一時的に無効にします。

問題

これは通常は問題なく動作します。

ただし、一部の(まれなケースですが)RecyclerViewでの遅いスクロールは、セミブロック化されます。つまり、下にスクロールしたときにスクロールバックしようとします。 2つのスクロールが互いに戦う(スクロールアップとスクロールダウン)ようです。

enter image description here

これをトリガーするコードは次のとおりです。

res/layout/activity_scrolling.xml

<Android.support.design.widget.CoordinatorLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/app_bar"
        Android:layout_width="match_parent"
        Android:layout_height="@dimen/app_bar_height"
        Android:fitsSystemWindows="true"
        Android:theme="@style/AppTheme.AppBarOverlay">

        <Android.support.design.widget.CollapsingToolbarLayout
            Android:id="@+id/toolbar_layout"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <Android.support.v7.widget.Toolbar
                Android:id="@+id/toolbar"
                Android:layout_width="match_parent"
                Android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        </Android.support.design.widget.CollapsingToolbarLayout>
    </Android.support.design.widget.AppBarLayout>

    <Android.support.v7.widget.RecyclerView
        Android:id="@+id/nestedView"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

    <LinearLayout
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:orientation="horizontal"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end">

        <Button
            Android:id="@+id/disableNestedScrollingButton"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="disable"/>

        <Button
            Android:id="@+id/enableNestedScrollingButton"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="enable"
            />
    </LinearLayout>

</Android.support.design.widget.CoordinatorLayout>

ScrollingActivity.Java

public class ScrollingActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        final RecyclerView nestedView = (RecyclerView) findViewById(R.id.nestedView);
        findViewById(R.id.disableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                nestedView.setNestedScrollingEnabled(false);
            }
        });
        findViewById(R.id.enableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                nestedView.setNestedScrollingEnabled(true);
            }
        });
        nestedView.setLayoutManager(new LinearLayoutManager(this));
        nestedView.setAdapter(new Adapter() {
            @Override
            public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
                return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(
                        Android.R.layout.simple_list_item_1,
                        parent,
                        false)) {
                };
            }

            @Override
            public void onBindViewHolder(final ViewHolder holder, final int position) {
                ((TextView) holder.itemView.findViewById(Android.R.id.text1)).setText("item " + position);
            }

            @Override
            public int getItemCount() {
                return 100;
            }
        });
    }

}

私が試したこと

最初は別の原因だと思いました(DrawerLayoutとの奇妙な組み合わせだと思いました)が、それを表示するための最小限のサンプルが見つかりました。それは、思ったとおりです。

これについては、Googleのウェブサイト( here )で報告しようとしましたが、実際のバグであれば修正されることを期待しています。試してみたい場合や、問題の動画をご覧になりたい場合は、こちらにアクセスしてください。ここにすべてをアップロードすることはできません(ファイルが大きすぎて多すぎる)。

他の投稿の指示に従って特別なフラグを使用しようとしました(例: herehere ここここ および ここ )、しかし何も助けにはなりませんでした。実際、拡張モードのままか、スクロールが私とは異なる方法で行われるかに関係なく、それぞれに問題がありました。

質問

  1. これは既知の問題ですか?なぜそれが起こるのですか?

  2. これを克服する方法はありますか?

  3. SetNestedScrollingEnabledのこの関数を呼び出す代わりの方法はおそらくありますか? CollapsingToolbarLayoutの状態をスクロールまたはロックする問題のないものですか?

20

これは この答え と同じ目標を達成するための代替アプローチです。その回答はリフレクションを使用していましたが、この回答は使用していませんが、理由は同じです。

なぜこれが起こっているのですか?

問題は、RecyclerViewがメンバー変数mScrollOffsetに古い値を使用する場合があることです。 mScrollOffsetは、RecyclerViewdispatchNestedPreScrolldispatchNestedScrollの2か所にのみ設定されます。 dispatchNestedPreScrollのみに関係しています。このメソッドは、RecyclerView#onTouchEventイベントを処理するときにMotionEvent.ACTION_MOVEによって呼び出されます。

以下は dispatchNestedPreScroll のドキュメントからの抜粋です。

dispatchNestedPreScroll

ブールdispatchNestedPreScroll(int dx、int dy、int []消費、int [] offsetInWindow)

このビューがその一部を消費する前に、進行中のネストされたスクロールの1ステップをディスパッチします。

ネストされたプレスクロールイベントは、タッチインターセプトがタッチするネストされたスクロールイベントに対するものです。 dispatchNestedPreScrollは、ネストされたスクロール操作の親ビューが、子ビューが消費する前にスクロール操作の一部またはすべてを消費する機会を提供します。

...

offsetInWindow int:オプション。 nullでない場合は、戻り時に、この操作の前から完了後までの、このビューのローカルビュー座標のオフセットが含まれます。ビューの実装はこれを使用して、予想される入力座標の追跡を調整できます。

offsetInWindowは実際にはint[2]であり、2番目のインデックスは、ネストされたスクロールのためにRecyclerViewに適用されるyシフトを表します。

RecyclerView#DispatchNestedPrescrollは、 NestedScrollingChildHelper で同じ名前のメソッドに解決されます。

RecyclerViewdispatchNestedPreScrollを呼び出すと、mScrollOffsetoffsetInWindow引数として使用されます。したがって、offsetInWindowに加えられた変更はmScrollOffsetを直接更新します。 dispatchNestedPreScroll更新mScrollOffset ネストされたスクロールが有効である限り。ネストされたスクロールが有効でない場合、mScrollOffsetは更新されず、 最後に設定された値で続行します dispatchNestedPreScroll。したがって、ネストされたスクロールがオフになると、mScrollOffsetの値はすぐに古くなりますが、RecyclerViewはそれを使い続けます。

dispatchNestedPreScrollから戻ったときのmScrollOffset[1]の正しい値は、input coordinate trackingに対して調整する量です(上記を参照)。 RecyclerViewでは、次の行がyタッチ座標を調整します。

mLastTouchY = y - mScrollOffset[1];

mScrollOffset[1]が-30であるとしましょう(古くてゼロでなければならないため)mLastTouchYは+30ピクセル(--30 = + 30)オフになります。この誤算の影響は、タッチが実際よりも画面の下で発生したように見えることです。したがって、遅い下向きのスクロールは実際には上にスクロールし、上向きのスクロールはより速くスクロールします。 (下向きのスクロールがこの30pxの障壁を克服するのに十分な速さである場合、下向きのスクロールが発生しますが、必要以上に遅くなります。)アプリがより多くのスペースをカバーしていると考えるため、上向きのスクロールは速すぎます。

mScrollOffsetは、ネストされたスクロールがオンになり、dispatchNestedPreScrollmScrollOffsetの正しい値を再度報告するまで、古い変数として継続します。

アプローチ

mScrollOffset[1]は特定の状況下で古い値を持っているため、それらの状況下では正しい値に設定することが目標です。ネストされたスクロールが行われていない場合、つまり、AppBarが展開または縮小されている場合、この値はゼロでなければなりません。残念ながら、mScrollOffsetRecyclerViewに対してローカルであり、そのためのセッターはありません。 Reflectionを使用せずにmScrollOffsetにアクセスするには、RecyclerViewをオーバーライドするカスタムdispatchNestedPreScrollを作成します。 4番目の引数はoffsetInWindowで、これは変更する必要がある変数です。

ネストされたスクロールがmScrollOffsetに対して無効になっている場合は常に、古いRecyclerViewが発生します。私たちが課す追加の条件は、AppBarがアイドルである必要があることです。これにより、mScrollOffset[1]はゼロであると安全に言えます。 CollapsingToolbarLayoutはスクロールフラグでsnapを指定しているため、これは問題ではありません。

サンプルアプリでは、ScrollingActivityが変更され、AppBarが展開されて閉じられたときに記録されるようになりました。 2つの条件が満たされたときにclampPrescrollOffsetListenerを返すコールバックも作成されました(true)。オーバーライドされたdispatchNestedPreScrollはこのコールバックを呼び出し、true応答でmScrollOffset[1]をゼロにクランプします。

カスタムRecyclerView - MyRecyclerViewと同様に、ScrollingActivityの更新されたソースファイルを以下に示します。 XMLレイアウトファイルは、カスタムMyRecyclerViewを反映するように変更する必要があります。

ScrollingActivity

public class ScrollingActivity extends AppCompatActivity
        implements MyRecyclerView.OnClampPrescrollOffsetListener {

    private CollapsingToolbarLayout mCollapsingToolbarLayout;
    private AppBarLayout mAppBarLayout;
    private MyRecyclerView mNestedView;
    // This variable will be true when the app bar is completely open or completely collapsed.
    private boolean mAppBarIdle = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_scrolling);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        mNestedView = (MyRecyclerView) findViewById(R.id.nestedView);
        mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar);
        mCollapsingToolbarLayout = (CollapsingToolbarLayout) findViewById(R.id.toolbar_layout);

        // Set the listener for the patch code.
        mNestedView.setOnClampPrescrollOffsetListener(this);

        // Listener to determine when the app bar is collapsed or fully open (idle).
        mAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
            @Override
            public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                mAppBarIdle = verticalOffset == 0
                        || verticalOffset <= appBarLayout.getTotalScrollRange();
            }
        });
        findViewById(R.id.disableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                // If the AppBar is fully expanded or fully collapsed (idle), then disable
                // expansion and apply the patch; otherwise, set a flag to disable the expansion
                // and apply the patch when the AppBar is idle.
                setExpandEnabled(false);

            }
        });
        findViewById(R.id.enableNestedScrollingButton).setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                setExpandEnabled(true);
            }
        });
        mNestedView.setLayoutManager(new LinearLayoutManager(this));
        mNestedView.setAdapter(new Adapter() {
            @Override
            public ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
                return new ViewHolder(LayoutInflater.from(parent.getContext()).inflate(
                        Android.R.layout.simple_list_item_1,
                        parent,
                        false)) {
                };
            }

            @Override
            public void onBindViewHolder(final ViewHolder holder, final int position) {
                ((TextView) holder.itemView.findViewById(Android.R.id.text1)).setText("item " + position);
            }

            @Override
            public int getItemCount() {
                return 100;
            }
        });
    }

    private void setExpandEnabled(boolean enabled) {
        mNestedView.setNestedScrollingEnabled(enabled);
    }

    // Return "true" when the app bar is idle and nested scrolling is disabled. This is a signal
    // to the custom RecyclerView to clamp the y prescroll offset to zero.
    @Override
    public boolean clampPrescrollOffsetListener() {
        return mAppBarIdle && !mNestedView.isNestedScrollingEnabled();
    }

    private static final String TAG = "ScrollingActivity";
}

MyRecyclerView

public class MyRecyclerView extends RecyclerView {
    private OnClampPrescrollOffsetListener mPatchListener;

    public MyRecyclerView(Context context) {
        super(context);
    }

    public MyRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyRecyclerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    // Just a call to super plus code to force offsetInWindow[1] to zero if the patchlistener
    // instructs it.
    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        boolean returnValue;
        int currentOffset;
        returnValue = super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
        currentOffset = offsetInWindow[1];
        Log.d(TAG, "<<<<dispatchNestedPreScroll: " + currentOffset);
        if (mPatchListener.clampPrescrollOffsetListener() && offsetInWindow[1] != 0) {
            Log.d(TAG, "<<<<dispatchNestedPreScroll: " + currentOffset + " -> 0");
            offsetInWindow[1] = 0;
        }
        return returnValue;
    }

    public void setOnClampPrescrollOffsetListener(OnClampPrescrollOffsetListener patchListener) {
        mPatchListener = patchListener;
    }

    public interface OnClampPrescrollOffsetListener {
        boolean clampPrescrollOffsetListener();
    }

    private static final String TAG = "MyRecyclerView";
}
3
Cheticamp

実際には、問題を間違った方法で見ている可能性があります。

必要なのは、それに応じてToolbarフラグを設定することだけです。あなたは本当に他には何もないので、私はあなたのレイアウトを次のように簡略化すべきだと言います:

_<Android.support.design.widget.CoordinatorLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">

    <Android.support.design.widget.AppBarLayout
         Android:id="@+id/app_bar"
         Android:layout_width="match_parent"
         Android:layout_height="@dimen/app_bar_height"
         Android:fitsSystemWindows="true"
         Android:theme="@style/AppTheme.AppBarOverlay">

        <Android.support.v7.widget.Toolbar
            Android:id="@+id/toolbar"
            Android:layout_width="match_parent"
            Android:layout_height="?attr/actionBarSize"
            app:layout_scrollFlags="scroll|enterAlways"
            app:popupTheme="@style/AppTheme.PopupOverlay"
            app:title="Title" />

    </Android.support.design.widget.AppBarLayout>

    <Android.support.v7.widget.RecyclerView
        Android:id="@+id/nestedView"
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"            
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

    <LinearLayout
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:orientation="horizontal"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end">

        <Button
            Android:id="@+id/disableNestedScrollingButton"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="disable"/>

        <Button
            Android:id="@+id/enableNestedScrollingButton"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="enable"
            />
    </LinearLayout>
</Android.support.design.widget.CoordinatorLayout>
_

次に、折りたたみを無効にする場合は、ツールバーのフラグを設定します。

_// To disable collapsing
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SNAP);
toolbar.setLayoutParams(params);
_

そして有効にする

_// To enable collapsing
AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) toolbar.getLayoutParams();
params.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL|AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
toolbar.setLayoutParams(params);
_

常に取得するのではなく変更する場合は、レイアウトパラメータへの参照を保持します。

必要な場合CollapsingToolbarLayoutget fromおよびsetLayoutParams to that View代わりに、フラグを同じ方法で更新しますが、appBarLayout.setExpanded(true/false)を追加します

注:setScrollFlagsを使用すると、以前のフラグがすべてクリアされるため、この方法を使用する場合は注意してすべて必須フラグを設定してください。

4
Joaquim Ley

リサイクルビュー内で、スムーズにスクロールします

Android:nestedScrollingEnabled="false" 

ツールバーのcardViewを重ねる

 app:behavior_overlapTop = "24dp" 

CollapsingToolbarに対して次のコードを試してください。

  <Android.support.design.widget.CoordinatorLayout
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:background="@color/background"
    Android:fitsSystemWindows="true">

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/app_bar"
        Android:layout_width="match_parent"
        Android:layout_height="@dimen/app_bar_height"
        Android:fitsSystemWindows="true"
        Android:theme="@style/AppTheme.AppBarOverlay">

        <Android.support.design.widget.CollapsingToolbarLayout
            Android:id="@+id/toolbar_layout"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <Android.support.v7.widget.Toolbar
                Android:id="@+id/toolbar"
                Android:layout_width="match_parent"
                Android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"
                app:title="Title" />

        </Android.support.design.widget.CollapsingToolbarLayout>
    </Android.support.design.widget.AppBarLayout>


    <Android.support.v4.widget.NestedScrollView
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:layout_marginLeft="10dp"
        Android:layout_marginRight="10dp"
        Android:background="@Android:color/transparent"
        app:behavior_overlapTop="@dimen/behavior_overlap_top"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout
            Android:id="@+id/linearLayout"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:orientation="vertical">

            <Android.support.v7.widget.RecyclerView
                Android:id="@+id/recycler_view
                Android:layout_width="match_parent"
                Android:layout_height="wrap_content"
                Android:layout_margin="@dimen/text_min_padding"
                Android:nestedScrollingEnabled="false"
                Android:scrollbarSize="2dp"
                Android:scrollbarStyle="outsideInset"
                Android:scrollbarThumbVertical="@color/colorAccent"
                Android:scrollbars="vertical" />

        </LinearLayout>

    </Android.support.v4.widget.NestedScrollView>

</Android.support.design.widget.CoordinatorLayout>

スクリーンショット

3
RamaKrishnan

@Moinkhanが指摘しているように、RecyclerViewと次の要素を次のようにNestedScrollViewでラップすると、折りたたみツールバーのレイアウトと一緒にスクロールする問題が解決します。

<Android.support.design.widget.CoordinatorLayout
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    xmlns:app="http://schemas.Android.com/apk/res-auto"
    xmlns:tools="http://schemas.Android.com/tools"
    Android:layout_width="match_parent"
    Android:layout_height="match_parent"
    Android:fitsSystemWindows="true"
    tools:context="com.example.user.myapplication.ScrollingActivity">

    <Android.support.design.widget.AppBarLayout
        Android:id="@+id/app_bar"
        Android:layout_width="match_parent"
        Android:layout_height="@dimen/app_bar_height"
        Android:fitsSystemWindows="true"
        Android:theme="@style/AppTheme.AppBarOverlay">

        <Android.support.design.widget.CollapsingToolbarLayout
            Android:id="@+id/toolbar_layout"
            Android:layout_width="match_parent"
            Android:layout_height="match_parent"
            Android:fitsSystemWindows="true"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">

            <Android.support.v7.widget.Toolbar
                Android:id="@+id/toolbar"
                Android:layout_width="match_parent"
                Android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin"
                app:popupTheme="@style/AppTheme.PopupOverlay"/>

        </Android.support.design.widget.CollapsingToolbarLayout>
    </Android.support.design.widget.AppBarLayout>

    <Android.support.v4.widget.NestedScrollView
        Android:layout_width="match_parent"
        Android:layout_height="match_parent"
        Android:layout_gravity="fill_vertical"
        Android:fillViewport="true"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <RelativeLayout
            Android:layout_width="match_parent"
            Android:layout_height="match_parent">

            <Android.support.v7.widget.RecyclerView
                Android:id="@+id/nestedView"
                Android:layout_width="match_parent"
                Android:layout_height="match_parent"
                app:layout_behavior="@string/appbar_scrolling_view_behavior"/>

        </RelativeLayout>

    </Android.support.v4.widget.NestedScrollView>

    <LinearLayout
        Android:layout_width="wrap_content"
        Android:layout_height="wrap_content"
        Android:orientation="horizontal"
        app:layout_anchor="@id/app_bar"
        app:layout_anchorGravity="bottom|end">

        <Button
            Android:id="@+id/disableNestedScrollingButton"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="disable"/>

        <Button
            Android:id="@+id/enableNestedScrollingButton"
            Android:layout_width="wrap_content"
            Android:layout_height="wrap_content"
            Android:text="enable"
            />
    </LinearLayout>

</Android.support.design.widget.CoordinatorLayout>

Recyclerviewのコンテンツが表示されない場合は、このスレッドに従って問題を解決できます NestedScrollView内でRecyclerViewを使用する方法

それが役に立てば幸い。

3
fmaccaroni

私は同様の問題を解決する必要があり、AppBarLayoutのカスタム動作を使用してそれを行いました。すべてがうまくいきます。カスタムの動作でonStartNestedScrollをオーバーライドすることで、スクロールビュー(NestedScrollView)を維持しながら、ツールバーのレイアウトの折りたたみを展開または折りたたむようにブロックし、期待どおりに機能させることができます。詳細を説明しました こちら 、お役に立てば幸いです。

private class AppBarLayoutBehavior : AppBarLayout.Behavior() {
    var canDrag = true
    var acceptsNestedScroll = true

    init {
        setDragCallback(object : AppBarLayout.Behavior.DragCallback() {
            override fun canDrag(appBarLayout: AppBarLayout): Boolean {
                // Allow/Do not allow dragging down/up to expand/collapse the layout
                return canDrag
            }
        })
    }

    override fun onStartNestedScroll(parent: CoordinatorLayout,
                                     child: AppBarLayout,
                                     directTargetChild: View,
                                     target: View,
                                     nestedScrollAxes: Int,
                                     type: Int): Boolean {
        // Refuse/Accept any nested scroll event
        return acceptsNestedScroll
    }}
2

次のコードを使用してください、それは私にとってはうまくいきます:

lockAppBarClosed();
ViewCompat.setNestedScrollingEnabled(recyclerView, false);   // to lock the CollapsingToolbarLayout

次のメソッドを実装します。

private void setAppBarDragging(final boolean isEnabled) {
        CoordinatorLayout.LayoutParams params =
                (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams();
        AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
        behavior.setDragCallback(new AppBarLayout.Behavior.DragCallback() {
            @Override
            public boolean canDrag(AppBarLayout appBarLayout) {
                return isEnabled;
            }
        });
        params.setBehavior(behavior);
    }

    public void unlockAppBarOpen() {
        appBarLayout.setExpanded(true, false);
        appBarLayout.setActivated(true);
        setAppBarDragging(false);
    }

    public void lockAppBarClosed() {
        appBarLayout.setExpanded(false, false);
        appBarLayout.setActivated(false);
        setAppBarDragging(false);

    }
2
Usman Rana

この問題は、折りたたみツールバーが所定の位置(閉じた状態または開いた状態)にスナップし、垂直オフセット変数(RecyclerViewmScrollOffset[1])がゼロ以外の値になり、その後にスクロール-スクロールを一方向に遅らせたり逆にしたりして、もう一方の方向にスピードを上げます。この変数は、ネストされたスクロールが有効な場合にのみNestedScrollingChildHelperに設定されるようです。したがって、ネストスクロールが無効になると、mScrollOffset[1]の値は変更されません。

この問題を確実に再現するには、ツールバーを所定の位置にスナップさせて、すぐに[無効]をクリックします。デモについては このビデオ を参照してください。問題の大きさは、「スナップ」がどれだけ発生するかによって異なると思います。

ツールバーを完全に開いた位置または閉じた位置にドラッグし、「スナップ」しないようにすると、この問題を再現できず、mScrollOffset[1]がゼロに設定されます。これは正しい値だと思います。また、レイアウトの折りたたみツールバーのlayout_scrollFlagsからsnapを削除して、ツールバーを部分的に開いた状態にすることで問題を再現しました。

これを試してみたい場合は、デモアプリをデバッグモードにして、mScrollOffset[1]RecyclerView#onTouchEventの値を確認できます。また、NestedScrollingChildHelperdispatchNestedScrollおよびdispatchNestedPreScrollメソッドを見て、ネストされたスクロールが有効な場合にのみオフセットが設定される方法を確認してください。

それで、これを修正するにはどうすればよいですか? mScrollOffsetはプライベートto RecyclerViewであり、mScrollOffset[1]の値を変更するために何かをサブクラス化する方法はすぐにはわかりません。それはリフレクションを残しますが、それはあなたにとって望ましくないかもしれません。多分別の読者がこれにどのように取り組むかについて考えを持っているか、いくつかの秘密のソースを知っています。 何か起こった場合は再投稿します。

編集:この問題を克服する新しいScrollingActivity.Javaクラスを提供しました。これはリフレクションを使用し、無効化スクロールボタンが押されてAppBarがアイドル状態のときにRecyclerViewmScrollOffset[1]をゼロに設定するパッチを適用します。私はいくつかの予備テストを行っており、それは機能しています。 これが Gist です。 (下記の更新された要旨を参照してください。)

2番目の編集:パッチを適用せずにツールバーを面白い方法でスナップし、途中で動かなくなるようにできたので、パッチはその特定の問題を引き起こしています。パッチが適用されていないアプリで十分に速く下にスクロールすることで、ツールバーを完全に開いた状態から折りたたんだ状態に跳ね返らせることができます。

また、パッチが何をしているかを再確認し、それ自体が動作すると思います。変数はプライベートであり、スクロールをオフにした後、1つの場所でのみ参照されます。スクロールを有効にすると、変数は使用前に常にリセットされます。本当の答えは、Googleがこの問題を修正することです。彼らがそうするまで、私はこれがあなたがこの特定のデザインで許容できる回避策を得ることができる最も近いかもしれないと思います。 (私は 更新された要旨 を投稿しました。これは、クイッククリックアラウンドで潜在的な問題に対処し、スイッチを潜在的な不適切な状態のままにします。)

いずれにせよ、根本的な問題が特定されており、問題を再現する信頼できる方法があるため、他の提案されたソリューションをより簡単に検証できます。

これがお役に立てば幸いです。

1
Cheticamp

here に主に基づいた素敵な代替案を提示したい:

AppBarLayoutEx.kt

class AppBarLayoutEx : AppBarLayout {
    private var isAppBarExpanded = true
    private val behavior = AppBarLayoutBehavior()
    private var onStateChangedListener: (Boolean) -> Unit = {}
    var enableExpandAndCollapseByDraggingToolbar: Boolean
        get() = behavior.canDrag
        set(value) {
            behavior.canDrag = value
        }

    var enableExpandAndCollapseByDraggingContent: Boolean
        get() = behavior.acceptsNestedScroll
        set(value) {
            behavior.acceptsNestedScroll = value
        }

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    init {
        addOnOffsetChangedListener(
                AppBarLayout.OnOffsetChangedListener { _, verticalOffset ->
                    isAppBarExpanded = verticalOffset == 0
                    onStateChangedListener(isAppBarExpanded)
                })
    }

    override fun setLayoutParams(params: ViewGroup.LayoutParams?) {
        super.setLayoutParams(params)
        (params as CoordinatorLayout.LayoutParams).behavior = behavior
    }

    fun toggleExpandedState() {
        setExpanded(!isAppBarExpanded, true)
    }

    fun setOnExpandAndCollapseListener(onStateChangedListener: (Boolean) -> Unit) {
        this.onStateChangedListener = onStateChangedListener
    }

    private class AppBarLayoutBehavior : AppBarLayout.Behavior() {
        var canDrag = true
        var acceptsNestedScroll = true

        init {
            setDragCallback(object : AppBarLayout.Behavior.DragCallback() {
                override fun canDrag(appBarLayout: AppBarLayout) = canDrag
            })
        }

        override fun onStartNestedScroll(parent: CoordinatorLayout, child: AppBarLayout, directTargetChild: View,
                                         target: View, nestedScrollAxes: Int, type: Int) = acceptsNestedScroll
    }
}

使用法:レイアウトXMLファイルで使用する以外に、次を使用して展開を無効/有効にすることができます。

appBarLayout.enableExpandAndCollapseByDraggingToolbar = true/false

appBarLayout.enableExpandAndCollapseByDraggingContent = true/false
0