ユーザーがスナックバーをスワイプして閉じるのを防ぐ方法はありますか?
ネットワークログイン中にスナックバーを表示するアプリがありますが、却下されないようにしたいと思います。
Nikola Despotoskiの提案によると、私は両方のソリューションを実験しました。
private void startSnack(){
loadingSnack = Snackbar.make(findViewById(R.id.email_login_form), getString(R.string.logging_in), Snackbar.LENGTH_INDEFINITE)
.setAction("CANCEL", new OnClickListener() {
@Override
public void onClick(View view) {
getOps().cancelLogin();
enableControls();
}
});
loadingSnack.getView().setOnTouchListener(new View.OnTouchListener() {
public long mInitialTime;
@Override
public boolean onTouch(View v, MotionEvent event) {
if (v instanceof Button) return false; //Action view was touched, proceed normally.
else {
switch (MotionEventCompat.getActionMasked(event)) {
case MotionEvent.ACTION_DOWN: {
Log.i(TAG, "ACTION_DOWN");
mInitialTime = System.currentTimeMillis();
break;
}
case MotionEvent.ACTION_UP: {
Log.i(TAG, "ACTION_UP");
long clickDuration = System.currentTimeMillis() - mInitialTime;
if (clickDuration <= ViewConfiguration.getTapTimeout()) {
return false;// click event, proceed normally
}
}
case MotionEvent.ACTION_MOVE: {
Log.i(TAG, "ACTION_MOVE");
return true;
}
}
return true;
}
}
});
ViewGroup.LayoutParams lp = loadingSnack.getView().getLayoutParams();
if (lp != null && lp instanceof CoordinatorLayout.LayoutParams) {
((CoordinatorLayout.LayoutParams)lp).setBehavior(new DummyBehavior());
loadingSnack.getView().setLayoutParams(lp);
Log.i(TAG, "Dummy behavior assigned to " + lp.toString());
}
loadingSnack.show();
}
これはDummyBehaviorクラスです:
public class DummyBehavior extends CoordinatorLayout.Behavior<View>{
/**
* Debugging tag used by the Android logger.
*/
protected final static String TAG =
DummyBehavior.class.getSimpleName();
public DummyBehavior() {
Log.i(TAG, "Dummy behavior created");
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
Log.i(TAG, "Method " + stackTrace[2].getMethodName() );
}
public DummyBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i(TAG, "Dummy behavior created");
}
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
return false;
}
@Override
public boolean onTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
return false;
}
@Override
public boolean blocksInteractionBelow(CoordinatorLayout parent, View child) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
return false;
}
@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
return false;
}
@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
return false;
}
@Override
public void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
}
@Override
public boolean isDirty(CoordinatorLayout parent, View child) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
return false;
}
@Override
public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
return false;
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
return false;
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
return false;
}
@Override
public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
}
@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
}
@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY, boolean consumed) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
return false;
}
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
return false;
}
@Override
public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout, View child, WindowInsetsCompat insets) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
return null;
}
@Override
public void onRestoreInstanceState(CoordinatorLayout parent, View child, Parcelable state) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
}
@Override
public Parcelable onSaveInstanceState(CoordinatorLayout parent, View child) {
Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
return null;
}
}
しかし、スワイプするとスナックバーが消えます。これは典型的なログです。
12-02 22:26:43.864 19598-19598/ I/DummyBehavior: Dummy behavior created
12-02 22:26:43.866 19598-19598/ I/DummyBehavior: Method <init>
12-02 22:26:43.866 19598-19598/ I/LifecycleLoggingActivity: Dummy behavior assigned to Android.support.design.widget.CoordinatorLayout$LayoutParams@808c0e9
12-02 22:26:44.755 19598-19598/ I/LifecycleLoggingActivity: ACTION_DOWN
12-02 22:26:44.798 19598-19598/ I/LifecycleLoggingActivity: ACTION_MOVE
12-02 22:26:44.815 19598-19598/ I/LifecycleLoggingActivity: ACTION_MOVE
12-02 22:26:44.832 19598-19598/ I/LifecycleLoggingActivity: ACTION_MOVE
12-02 22:26:44.849 19598-19598/ I/LifecycleLoggingActivity: ACTION_MOVE
12-02 22:26:44.866 19598-19598/ I/LifecycleLoggingActivity: ACTION_MOVE
12-02 22:26:44.883 19598-19598/ I/LifecycleLoggingActivity: ACTION_MOVE
これは私のために働いた:
Snackbar.SnackbarLayout layout = (Snackbar.SnackbarLayout) snackbar.getView();
snackbar.setDuration(Snackbar.LENGTH_INDEFINITE);
snackbar.show();
layout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
ViewGroup.LayoutParams lp = layout.getLayoutParams();
if (lp instanceof CoordinatorLayout.LayoutParams) {
((CoordinatorLayout.LayoutParams) lp).setBehavior(new DisableSwipeBehavior());
layout.setLayoutParams(lp);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
//noinspection deprecation
layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
});
DisableSwipeBehaviorの場所:
public class DisableSwipeBehavior extends SwipeDismissBehavior<Snackbar.SnackbarLayout> {
@Override
public boolean canSwipeDismissView(@NonNull View view) {
return false;
}
}
Snackbarは、setBehaviorメソッドを使用して、これを実際にサポートするようになりました。ここでの素晴らしい点は、以前は常にいくつかの動作が失われ、現在は保持されていることです。
パッケージが移動したため、「新しい」をインポートする必要があることに注意してください スナックバーパッケージのスナックバー 。
Snackbar.make(view, stringId, Snackbar.LENGTH_LONG)
.setBehavior(new NoSwipeBehavior())
.show();
class NoSwipeBehavior extends BaseTransientBottomBar.Behavior {
@Override
public boolean canSwipeDismissView(View child) {
return false;
}
}
これは私のために働いた:
Snackbar snackbar = Snackbar.make(findViewById(container), R.string.offers_refreshed, Snackbar.LENGTH_LONG);
final View snackbarView = snackbar.getView();
snackbar.show();
snackbarView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
snackbarView.getViewTreeObserver().removeOnPreDrawListener(this);
((CoordinatorLayout.LayoutParams) snackbarView.getLayoutParams()).setBehavior(null);
return true;
}
});
幸運を! :)
Snackbar
ビューをクリックするのではなく、ストリーミングタッチイベントを無効にすることができます。
mSnackBar.getView().setOnTouchListener(new View.OnTouchListener() {
public long mInitialTime;
@Override
public boolean onTouch(View v, MotionEvent event) {
if (v instanceof Button) return false; //Action view was touched, proceed normally.
else {
switch (MotionEventCompat.getActionMasked(event)) {
case MotionEvent.ACTION_DOWN: {
mInitialTime = System.currentTimeMillis();
break;
}
case MotionEvent.ACTION_UP: {
long clickDuration = System.currentTimeMillis() - mInitialTime;
if (clickDuration <= ViewConfiguration.getTapTimeout()) {
return false;// click event, proceed normally
}
}
}
return true;
}
});
または、Snackbar
の動作を空のCoordinatorLayout.Behavior
に置き換えることもできます。
public CouchPotatoBehavior extends CoordinatorLayout.Behavior<View>{
//override all methods and don't call super methods.
}
これは空の動作であり、何もしません。デフォルトのSwipeToDismissBehavior
はViewDragHelper
を使用してタッチイベントを処理し、タッチイベントが発生すると却下されます。
ViewGroup.LayoutParams lp = mSnackbar.getView().getLayoutParams();
if (lp instanceof CoordinatorLayout.LayoutParams) {
((CoordinatorLayout.LayoutParams)lp).setBehavior(new CouchPotatoBehavior());
mSnackbar.getView().setLayoutParams(lp);
}
ここでのより良い解決策....スナックバーのビューとしてCoordinatorLayout
またはその子を提供しないでください。
Snackbar.make(ROOT_LAYOUT , "No internet connection", Snackbar.LENGTH_INDEFINITE).show();
どこ ROOT_LAYOUT
は、coordinatorlayoutまたはその子を除く任意のレイアウトである必要があります。
これは、ViewTreeObserver
をいじる必要のないソリューションです。次のソリューションは、SDK26に基づいてKotlinで記述されていることに注意してください。
BaseTransientBottomBar
final void showView() {
if (mView.getParent() == null) {
final ViewGroup.LayoutParams lp = mView.getLayoutParams();
if (lp instanceof CoordinatorLayout.LayoutParams) {
// If our LayoutParams are from a CoordinatorLayout, we'll setup our Behavior
final CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams) lp;
final Behavior behavior = new Behavior();
behavior.setStartAlphaSwipeDistance(0.1f);
behavior.setEndAlphaSwipeDistance(0.6f);
behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);
behavior.setListener(new SwipeDismissBehavior.OnDismissListener() {
@Override
public void onDismiss(View view) {
view.setVisibility(View.GONE);
dispatchDismiss(BaseCallback.DISMISS_EVENT_SWIPE);
}
@Override
public void onDragStateChanged(int state) {
switch (state) {
case SwipeDismissBehavior.STATE_DRAGGING:
case SwipeDismissBehavior.STATE_SETTLING:
// If the view is being dragged or settling, pause the timeout
SnackbarManager.getInstance().pauseTimeout(mManagerCallback);
break;
case SwipeDismissBehavior.STATE_IDLE:
// If the view has been released and is idle, restore the timeout
SnackbarManager.getInstance()
.restoreTimeoutIfPaused(mManagerCallback);
break;
}
}
});
clp.setBehavior(behavior);
// Also set the inset Edge so that views can dodge the bar correctly
clp.insetEdge = Gravity.BOTTOM;
}
mTargetParent.addView(mView);
}
...
}
メソッドBaseTransientBottomBar
でshowView
のソースコードを調べると、layoutParamsがCoordinatorLayout.LayoutParams
の場合の動作が追加されます。動作をnull
に戻すことで、これを元に戻すことができます。
ビューが表示される直前に動作が追加されるため、ビューが表示された後で元に戻す必要があります。
val snackbar = Snackbar.make(coordinatorLayout, "Hello World!", Snackbar.LENGTH_INDEFINITE)
snackbar.show()
snackbar.addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onShown(transientBottomBar: Snackbar) {
val layoutParams = transientBottomBar.view.layoutParams as? CoordinatorLayout.LayoutParams
layoutParams?.let { it.behavior = null }
}
})