CollapsingToolbarLayoutがあり、下部にRecyclerViewがある非常に複雑なレイアウトがあります。
場合によっては、RecyclerViewでsetNestedScrollingEnabled(boolean)を呼び出して、CollapsingToolbarLayoutの展開/折りたたみを一時的に無効にします。
これは通常は問題なく動作します。
ただし、一部の(まれなケースですが)RecyclerViewでの遅いスクロールは、セミブロック化されます。つまり、下にスクロールしたときにスクロールバックしようとします。 2つのスクロールが互いに戦う(スクロールアップとスクロールダウン)ようです。
これをトリガーするコードは次のとおりです。
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 )で報告しようとしましたが、実際のバグであれば修正されることを期待しています。試してみたい場合や、問題の動画をご覧になりたい場合は、こちらにアクセスしてください。ここにすべてをアップロードすることはできません(ファイルが大きすぎて多すぎる)。
他の投稿の指示に従って特別なフラグを使用しようとしました(例: here 、 here 、 ここ 、 ここ および ここ )、しかし何も助けにはなりませんでした。実際、拡張モードのままか、スクロールが私とは異なる方法で行われるかに関係なく、それぞれに問題がありました。
これは既知の問題ですか?なぜそれが起こるのですか?
これを克服する方法はありますか?
SetNestedScrollingEnabledのこの関数を呼び出す代わりの方法はおそらくありますか? CollapsingToolbarLayoutの状態をスクロールまたはロックする問題のないものですか?
これは この答え と同じ目標を達成するための代替アプローチです。その回答はリフレクションを使用していましたが、この回答は使用していませんが、理由は同じです。
なぜこれが起こっているのですか?
問題は、RecyclerView
がメンバー変数mScrollOffset
に古い値を使用する場合があることです。 mScrollOffset
は、RecyclerView
のdispatchNestedPreScroll
とdispatchNestedScroll
の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
で同じ名前のメソッドに解決されます。
RecyclerView
がdispatchNestedPreScroll
を呼び出すと、mScrollOffset
がoffsetInWindow
引数として使用されます。したがって、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
は、ネストされたスクロールがオンになり、dispatchNestedPreScroll
がmScrollOffset
の正しい値を再度報告するまで、古い変数として継続します。
アプローチ
mScrollOffset[1]
は特定の状況下で古い値を持っているため、それらの状況下では正しい値に設定することが目標です。ネストされたスクロールが行われていない場合、つまり、AppBarが展開または縮小されている場合、この値はゼロでなければなりません。残念ながら、mScrollOffset
はRecyclerView
に対してローカルであり、そのためのセッターはありません。 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";
}
実際には、問題を間違った方法で見ている可能性があります。
必要なのは、それに応じて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);
_
常に取得するのではなく変更する場合は、レイアウトパラメータへの参照を保持します。
必要な場合CollapsingToolbarLayout
get fromおよびsetLayoutParams
to that View
代わりに、フラグを同じ方法で更新しますが、appBarLayout.setExpanded(true/false)
を追加します
注:setScrollFlags
を使用すると、以前のフラグがすべてクリアされるため、この方法を使用する場合は注意してすべて必須フラグを設定してください。
リサイクルビュー内で、スムーズにスクロールします
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>
@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を使用する方法 。
それが役に立てば幸い。
私は同様の問題を解決する必要があり、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
}}
次のコードを使用してください、それは私にとってはうまくいきます:
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);
}
この問題は、折りたたみツールバーが所定の位置(閉じた状態または開いた状態)にスナップし、垂直オフセット変数(RecyclerView
のmScrollOffset[1]
)がゼロ以外の値になり、その後にスクロール-スクロールを一方向に遅らせたり逆にしたりして、もう一方の方向にスピードを上げます。この変数は、ネストされたスクロールが有効な場合にのみNestedScrollingChildHelper
に設定されるようです。したがって、ネストスクロールが無効になると、mScrollOffset[1]
の値は変更されません。
この問題を確実に再現するには、ツールバーを所定の位置にスナップさせて、すぐに[無効]をクリックします。デモについては このビデオ を参照してください。問題の大きさは、「スナップ」がどれだけ発生するかによって異なると思います。
ツールバーを完全に開いた位置または閉じた位置にドラッグし、「スナップ」しないようにすると、この問題を再現できず、mScrollOffset[1]
がゼロに設定されます。これは正しい値だと思います。また、レイアウトの折りたたみツールバーのlayout_scrollFlags
からsnap
を削除して、ツールバーを部分的に開いた状態にすることで問題を再現しました。
これを試してみたい場合は、デモアプリをデバッグモードにして、mScrollOffset[1]
のRecyclerView#onTouchEvent
の値を確認できます。また、NestedScrollingChildHelper
のdispatchNestedScroll
およびdispatchNestedPreScroll
メソッドを見て、ネストされたスクロールが有効な場合にのみオフセットが設定される方法を確認してください。
それで、これを修正するにはどうすればよいですか? mScrollOffset
はプライベートto RecyclerView
であり、mScrollOffset[1]
の値を変更するために何かをサブクラス化する方法はすぐにはわかりません。それはリフレクションを残しますが、それはあなたにとって望ましくないかもしれません。多分別の読者がこれにどのように取り組むかについて考えを持っているか、いくつかの秘密のソースを知っています。 何か起こった場合は再投稿します。
編集:この問題を克服する新しいScrollingActivity.Java
クラスを提供しました。これはリフレクションを使用し、無効化スクロールボタンが押されてAppBarがアイドル状態のときにRecyclerView
のmScrollOffset[1]
をゼロに設定するパッチを適用します。私はいくつかの予備テストを行っており、それは機能しています。 これが Gist です。 (下記の更新された要旨を参照してください。)
2番目の編集:パッチを適用せずにツールバーを面白い方法でスナップし、途中で動かなくなるようにできたので、パッチはその特定の問題を引き起こしています。パッチが適用されていないアプリで十分に速く下にスクロールすることで、ツールバーを完全に開いた状態から折りたたんだ状態に跳ね返らせることができます。
また、パッチが何をしているかを再確認し、それ自体が動作すると思います。変数はプライベートであり、スクロールをオフにした後、1つの場所でのみ参照されます。スクロールを有効にすると、変数は使用前に常にリセットされます。本当の答えは、Googleがこの問題を修正することです。彼らがそうするまで、私はこれがあなたがこの特定のデザインで許容できる回避策を得ることができる最も近いかもしれないと思います。 (私は 更新された要旨 を投稿しました。これは、クイッククリックアラウンドで潜在的な問題に対処し、スイッチを潜在的な不適切な状態のままにします。)
いずれにせよ、根本的な問題が特定されており、問題を再現する信頼できる方法があるため、他の提案されたソリューションをより簡単に検証できます。
これがお役に立てば幸いです。
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