Wyłączanie przeciągania przez użytkownika w arkuszu dolnym


100

Próbuję wyłączyć przeciąganie użytkownika BottomSheet. Powodem, dla którego chcę wyłączyć, są dwie rzeczy. 1. Zapobiega ListViewprzewijaniu w dół, 2. Nie chcę, aby użytkownicy zamykali za pomocą przeciągania, ale za pomocą przycisku na BottomSheetView. Oto, co zrobiłem

 bottomSheetBehavior = BottomSheetBehavior.from(bottomAnc);
    bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull View bottomSheet, int newState) {
            if (newState == BottomSheetBehavior.STATE_EXPANDED) {
                //Log.e("BottomSheet", "Expanded");
            } else if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
                //Log.e("BottomSheet", "Collapsed");
            }
        }

        @Override
        public void onSlide(@NonNull View bottomSheet, float slideOffset) {
            // React to dragging events
            bottomSheet.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    int action = MotionEventCompat.getActionMasked(event);
                    switch (action) {
                        case MotionEvent.ACTION_DOWN:
                            return false;
                        default:
                            return true;
                    }
                }
            });
        }
    });

Plik bottomSheetLayout

    <?xml version="1.0" encoding="utf-8"?><FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
app:behavior_hideable="true"
app:behavior_peekHeight="0dp"
app:layout_behavior="@string/bottom_sheet_behavior"
android:id="@+id/bottomSheet">

<android.support.v7.widget.CardView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:elevation="10dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            android:gravity="center_vertical">

            <TextView
                android:id="@+id/text1"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="Order Items"
                android:layout_margin="16dp"
                android:textAppearance="@android:style/TextAppearance.Large"/>


            <Button
                android:layout_width="50dp"
                android:layout_height="wrap_content"
                android:layout_marginRight="5dp"
                android:background="@drawable/bg_accept"/>

            <Button
                android:layout_width="50dp"
                android:layout_height="wrap_content"
                android:layout_marginRight="8dp"
                android:background="@drawable/bg_cancel"/>

        </LinearLayout>

        <ListView
            android:id="@+id/item_edit"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/white"
            android:divider="@color/md_divider_black"
            android:dividerHeight="1dp"/>

    </LinearLayout>

</android.support.v7.widget.CardView>


Proszę, sprawdź moją odpowiedź. Zauważyłem, że jest to bardziej trafne niż zaakceptowana odpowiedź
Vitalii Obideiko,

Odpowiedzi:


92

To może być już nieaktualne, ale zostawię to tutaj:

import android.content.Context
import android.util.AttributeSet
import androidx.coordinatorlayout.widget.CoordinatorLayout
import android.view.MotionEvent
import android.view.View
import com.google.android.material.bottomsheet.BottomSheetBehavior

@Suppress("unused")
class LockableBottomSheetBehavior<V : View> : BottomSheetBehavior<V> {
    constructor() : super()
    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    var swipeEnabled = true

    override fun onInterceptTouchEvent(
        parent: CoordinatorLayout,
        child: V,
        event: MotionEvent
    ): Boolean {
        return if (swipeEnabled) {
            super.onInterceptTouchEvent(parent, child, event)
        } else {
            false
        }
    }

    override fun onTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean {
        return if (swipeEnabled) {
            super.onTouchEvent(parent, child, event)
        } else {
            false
        }
    }

    override fun onStartNestedScroll(
        coordinatorLayout: CoordinatorLayout,
        child: V,
        directTargetChild: View,
        target: View,
        axes: Int,
        type: Int
    ): Boolean {
        return if (swipeEnabled) {
            super.onStartNestedScroll(
                coordinatorLayout,
                child,
                directTargetChild,
                target,
                axes,
                type
            )
        } else {
            false
        }
    }

    override fun onNestedPreScroll(
        coordinatorLayout: CoordinatorLayout,
        child: V,
        target: View,
        dx: Int,
        dy: Int,
        consumed: IntArray,
        type: Int
    ) {
        if (swipeEnabled) {
            super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed, type)
        }
    }

    override fun onStopNestedScroll(
        coordinatorLayout: CoordinatorLayout,
        child: V,
        target: View,
        type: Int
    ) {
        if (swipeEnabled) {
            super.onStopNestedScroll(coordinatorLayout, child, target, type)
        }
    }

    override fun onNestedPreFling(
        coordinatorLayout: CoordinatorLayout,
        child: V,
        target: View,
        velocityX: Float,
        velocityY: Float
    ): Boolean {
        return if (swipeEnabled) {
            super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY)
        } else {
            false
        }
    }
}

I użyj go w swoim pliku xml:

app:layout_behavior="com.your.package.LockableBottomSheetBehavior"

Wyłącza wszystkie akcje użytkowników, może być używany, gdy chcesz sterować arkuszem dolnym tylko programowo.


2
To najlepsza odpowiedź na wyłączenie BottomSheetBehaviour. Mężczyzna powyżej również opublikował podobne rozwiązanie, ale nie napisał, aby nadpisać inne zdarzenia, takie jak onTouchEvent () . Z drugiej strony możesz poprawić swoją odpowiedź, wstawiając flagę zamiast fałszu
murt

3
Jak używasz tego z BottomSheetFragment?
user3144836

7
Musisz konkretnie odwołać się do tej klasy w swoim XML. app: layout_behavior = "com.my.package.UserLockBottomSheetBehavior"
Steve

3
W niektórych przypadkach to nadal nie działa, jeśli mamy listę we fragmencie dolnego arkusza, nadal się ciągnie
Deepak Joshi

1
@DeepakJoshi może u może rozszerzyć RecyclerView i zastąpić kilka metod, takich jak `` hasNestedScrollingParent '', ale nie jestem pewien
Vitalii Obideiko

74

sprawdź stan w onStateChangedmetodzie setBottomSheetCallbackif state to BottomSheetBehavior.STATE_DRAGGINGzmień to tak, aby BottomSheetBehavior.STATE_EXPANDEDużytkownik mógł zatrzymać STATE_DRAGGING. jak poniżej

final BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
        behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
            @Override
            public void onStateChanged(@NonNull View bottomSheet, int newState) {
                if (newState == BottomSheetBehavior.STATE_DRAGGING) {
                    behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                }
            }

            @Override
            public void onSlide(@NonNull View bottomSheet, float slideOffset) {
            }
        });

użyj przycisku, aby otworzyć zamknij dolny arkusz, jak poniżej

fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (behavior.getState() == BottomSheetBehavior.STATE_HIDDEN) {
                    behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                } else {
                    behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
                }
            }
        });

nie używaj setPeekHeightlubapp:behavior_peekHeight

powyższym sposobem możesz osiągnąć swój cel


1
Niezła sztuczka. Nie zauważyłem tego. Dzięki. A także, czy możesz w tym pomóc. Kiedy każę mu się najpierw rozwinąć, jest przezroczysty i widzę widok z tyłu, ale nie mogę wchodzić w interakcje, dopóki nie stuknę w EditText w SheetView przed udostępnieniem go.
Tonespy

Zrobiłem mój BottomSheet View match_parenti za każdym razem, gdy próbuję go przywołać, Activityzauważyłem, że przesuwa się w górę, ale nie jest widoczny, dopóki nie EditTextKeyboardBottomSheet View
dotknę

1
Próbowałem tego, ale stany kończą się w STATE_SETTLING. Mam przycisk do otwierania i zamykania dolnego arkusza, jeśli jest UKRYTY, rozwijam go. Jeśli jest ROZSZERZONY, ukrywam to. Ponieważ utknął w ROZLICZENIU, mój przycisk nie działa po przeciągnięciu dolnego arkusza. Masz jakiś pomysł na to?
Gokhan Arik

3
To rozwiązanie jest zawodne; dolny arkusz przechodzi w zły stan, jak powiedział Gokhan ... a kiedy jest w tym złym stanie, wywołania takie jak załadowanie nowego fragmentu do dolnego arkusza po prostu znikną.
Ray W

7
To nie zadziała, jeśli masz zagnieżdżony widok przewijania w dolnym arkuszu
Rishabh Chandel

32

W porządku, więc zaakceptowana odpowiedź nie działa dla mnie. Jednak odpowiedź Виталий Обидейко zainspirowała moje ostateczne rozwiązanie.

Najpierw utworzyłem następujący niestandardowy BottomSheetBehavior. Zastępuje wszystkie metody związane z dotykiem i zwraca fałsz (lub nic nie zrobił), jeśli jest zablokowany. W przeciwnym razie zachowuje się jak normalne zachowanie BottomSheetBehavior. To wyłącza możliwość przeciągania w dół przez użytkownika i nie wpływa na zmianę stanu w kodzie.

LockableBottomSheetBehavior.java

public class LockableBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {

    private boolean mLocked = false;

    public LockableBottomSheetBehavior() {}

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

    public void setLocked(boolean locked) {
        mLocked = locked;
    }

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
        boolean handled = false;

        if (!mLocked) {
            handled = super.onInterceptTouchEvent(parent, child, event);
        }

        return handled;
    }

    @Override
    public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
        boolean handled = false;

        if (!mLocked) {
            handled = super.onTouchEvent(parent, child, event);
        }

        return handled;
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) {
        boolean handled = false;

        if (!mLocked) {
            handled = super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
        }

        return handled;
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed) {
        if (!mLocked) {
            super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        }
    }

    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
        if (!mLocked) {
            super.onStopNestedScroll(coordinatorLayout, child, target);
        }
    }

    @Override
    public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY) {
        boolean handled = false;

        if (!mLocked) {
            handled = super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
        }

        return handled;

    }
}

Oto przykład, jak go używać. W moim przypadku potrzebowałem tego, aby arkusz dolny zablokował się po rozwinięciu.

activity_home.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|snap"
            app:titleEnabled="false"/>
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"/>
    </android.support.design.widget.AppBarLayout>

    <!-- Use layout_behavior to set your Behavior-->
    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layoutManager="android.support.v7.widget.LinearLayoutManager"
        app:layout_behavior="com.myapppackage.LockableBottomSheetBehavior"/>

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

HomeActivity.java

public class HomeActivity extends AppCompatActivity {
    BottomSheetBehavior mBottomSheetBehavior;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        recyclerView.setAdapter(new SomeAdapter());

        mBottomSheetBehavior = BottomSheetBehavior.from(recyclerView);
        mBottomSheetBehavior.setBottomSheetCallback(new MyBottomSheetCallback());
    }

    class MyBottomSheetCallback extends BottomSheetBehavior.BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull View bottomSheet, int newState) {
            if (newState == BottomSheetBehavior.STATE_EXPANDED) {
                if (mBottomSheetBehavior instanceof LockableBottomSheetBehavior) {
                    ((LockableBottomSheetBehavior) mBottomSheetBehavior).setLocked(true);
                }
            }
        }

        @Override
        public void onSlide(@NonNull View bottomSheet, float slideOffset) {}
    });
}

Mam nadzieję, że pomoże to wyjaśnić wiele zamieszania!


1
Dobrze, najlepszą odpowiedzią jest to, że możemy uniknąć obejścia tych stanów, które prowadzą do pominięcia wydarzeń. Dziękuję Ci.
Tấn Nguyên

@James - Dobra odpowiedź, ale teraz nie mogę ustawićPeekHeight (). Dowolny pomysł?
Adarsh ​​Yadav

Próbowałem tego. mi to pasuje. dzięki bracie za uratowanie mojej dupy
Sup. Ia

1
To dobre obejście, chociaż nie jest obecnie aktualizowane. Metoda OnNestedPreScroll i niektóre inne metody są przestarzałe. Musisz zaktualizować te metody i działa dobrze.
Ajay

4
Witam, to nie działa na BottomSheetDialogFragment, nadal mogę przeciągnąć dolny arkusz
-do

23

Skończyło się na napisaniu obejścia, aby rozwiązać ten przypadek użycia dynamicznego wyłączania przeciągania użytkownika, w którym BottomSheetBehavior jest podklasą, aby zastąpić onInterceptTouchEvent i zignorować go, gdy niestandardowa flaga (w tym przypadku mAllowUserDragging) jest ustawiona na false:

import android.content.Context;
import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.CoordinatorLayout;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class WABottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {
    private boolean mAllowUserDragging = true;
    /**
     * Default constructor for instantiating BottomSheetBehaviors.
     */
    public WABottomSheetBehavior() {
        super();
    }

    /**
     * Default constructor for inflating BottomSheetBehaviors from layout.
     *
     * @param context The {@link Context}.
     * @param attrs   The {@link AttributeSet}.
     */
    public WABottomSheetBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setAllowUserDragging(boolean allowUserDragging) {
        mAllowUserDragging = allowUserDragging;
    }

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
        if (!mAllowUserDragging) {
            return false;
        }
        return super.onInterceptTouchEvent(parent, child, event);
    }
}

A w pliku XML układu:

    <FrameLayout
        android:id="@+id/bottom_sheet_frag_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:behavior_hideable="true"
        app:behavior_peekHeight="@dimen/bottom_sheet_peek_height"
        app:elevation="@dimen/bottom_sheet_elevation"
        app:layout_behavior="com.example.ray.WABottomSheetBehavior" />

Jak dotąd jest to najbardziej konsekwentne rozwiązanie do wyłączania przeciągania użytkownika na dolnym arkuszu na żądanie.

Wszystkie inne rozwiązania, które polegały na wywołaniu innego wywołania setState w wywołaniu zwrotnym onStateChanged, spowodowały, że BottomSheet przeszedł w zły stan lub spowodowało poważne problemy z UX (w przypadku opublikowania wywołania setState w Runnable).

Mam nadzieję, że to komuś pomoże :)

Promień


4
To całkiem fajne
Odys

3
@BeeingJk Zamiast FrameLayout użyj NestedScrollView i ustawbottomSheetFragContainer.setNestedScrollingEnabled(false);
AfzalivE

1
ROZWIĄZANE: przez ustawienie zachowania
LOG_TAG

4
To nie działa dla mnie! PS: Mam przewijalny tekst na dole strony
Thorvald Olavsen

6
Jak rzucasz to podczas inicjalizacji? To daje mi ostrzeżenie WABottomSheetBehavior <View> zachowanie = (WABottomSheetBehavior) BottomSheetBehavior.from (sheetView);
Leo Droidcoder

8

Późna odpowiedź, ale to właśnie zadziałało w moim przypadku, co różni się nieco od tego, co sugerowali inni.

Możesz spróbować ustawić cancelablewłaściwość na false, tj

setCancelable(false);

a następnie ręcznie obsługiwać zdarzenia, w których chciałbyś zamknąć okno dialogowe w setupDialogmetodzie.

@Override
public void setupDialog(final Dialog dialog, final int style) {

    // handle back button
    dialog.setOnKeyListener(new DialogInterface.OnKeyListener() {
        @Override
        public boolean onKey(final DialogInterface dialog, final int keyCode, final KeyEvent event) {
            if (keyCode == KeyEvent.KEYCODE_BACK) {
                dialog.dismiss();
            }
            return true;
        }
    });

    // handle touching outside of the dialog
    final View touchOutsideView = getDialog().getWindow().getDecorView().findViewById(android.support.design.R.id.touch_outside);
    touchOutsideView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(final View view) {
            dialog.dismiss();
        }
    });
}

Działa to z ListView wewnątrz fragmentu okna dialogowego, w którym utknąłem trochę z innymi rozwiązaniami.


Ładne, zwięzłe rozwiązanie. Dla każdego, kto to czyta, (prawdopodobnie) będziesz potrzebować dodatkowych sprawdzeń przed zamknięciem okna dialogowego event.isCanceled()i event.getAction() == MotionEvent.ACTION_UPprzed jego zamknięciem - zapobiegnie to błędnym kliknięciom wywołującym odrzucenie.
Eric Bachhuber

Dzięki za to. To najprostsze rozwiązanie, aby wyłączyć przeciąganie.
AVJ

7

Zaakceptowana odpowiedź nie działa na pierwszym urządzeniu testowym, którego używam. A odbicie nie jest gładkie. Wydaje się, że lepiej ustawić stan na STATE_EXPANDED tylko wtedy, gdy użytkownik zwolni przeciąganie. Oto moja wersja:

    final BottomSheetBehavior behavior = BottomSheetBehavior.from(findViewById(R.id.bottomSheet));
    behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull View bottomSheet, int newState) {
            if (newState > BottomSheetBehavior.STATE_DRAGGING)
                bottomSheet.post(new Runnable() {
                    @Override public void run() {
                        behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                    }
                });
        }

        @Override
        public void onSlide(@NonNull View bottomSheet, float slideOffset) {
        }
    });

1
Pozwól, że opowiem ci o problemie z wrzuceniem go do runnable, chyba że tego chcesz. Nie możesz go odrzucić za pomocą przycisku, ponieważ musi przeciągnąć, aby odrzucić. I zawsze będzie reagować na przeciąganie, tylko że uniemożliwiłoby to użytkownikowi przeciąganie w celu zwolnienia
Tonespy

7

Dodaj ten kod do obiektu BottomSheetBehavior . Przeciąganie zostanie wyłączone. U mnie działa dobrze.

final BottomSheetBehavior behavior = BottomSheetBehavior.from((View) view.getParent());
    behavior.setHideable(false);
    behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {

      @Override
      public void onStateChanged(@NonNull View bottomSheet, int newState) {
        if (newState == BottomSheetBehavior.STATE_DRAGGING) {
          behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
        }

      }
      @Override
      public void onSlide(@NonNull View bottomSheet, float slideOffset) {

      }
});

1
Nie wyłącza to przesuwania. Całkowicie zwija dolny arkusz.
Adam Hurwitz

7

Spodziewane zachowanie:

  • Arkusz dolny nie zamyka się podczas przeciągania w dół
  • Arkusz dolny zamyka się po dotknięciu poza oknem dialogowym

Kod:

class MyBottomSheet : BottomSheetDialogFragment () {

   override fun onActivityCreated(savedInstanceState: Bundle?) {
       super.onActivityCreated(savedInstanceState)
       disableBottomSheetDraggableBehavior()
   }

   private fun disableBottomSheetDraggableBehavior() {
      this.isCancelable = false
      this.dialog?.setCanceledOnTouchOutside(true)
   }

 }

Z jakiegoś powodu nie mogę zamknąć okna dialogowego dotykającego na zewnątrz, ale działa, aby wyłączyć przeciąganie
Gastón Saillén

5

Aby zablokować arkusz dolny i uniknąć przeciągnięcia go przez użytkownika, oto co zrobiłem

public void showBottomSheet() {
    bsb.setHideable(false);
    bsb.setState(BottomSheetBehavior.STATE_EXPANDED);
}

public void hideBottomSheet() {
    bsb.setHideable(true);
    bsb.setState(BottomSheetBehavior.STATE_COLLAPSED);
}

U mnie działa całkiem nieźle.


To rozwiązanie było atrakcyjne, ale co dziwne powoduje, że dolny arkusz pojawia się z góry ekranu zamiast z dołu! Znika jednak w normalny sposób. To bardzo Star Trek.
Tunga

Musiałem dokonać modyfikacji wzroku i zamiast tego użyć BottomSheetBehavior.STATE_HIDDEN. W takim przypadku nie możesz również dzwonić setPeekHeight(). Jest to znacznie mniej skomplikowane niż inne rozwiązania tutaj.
HolySamosa

5

Łatwy sposób na zablokowanie przeciągania jest ustawiony tak samo jak wysokość widoku. Na przykład:

private LinearLayout bottomSheet;
private BottomSheetBehavior bottomBehavior;

@Override
public void onResume() {
    super.onResume();
    bottomBehavior = BottomSheetBehavior.from((bottomSheet);
    bottomBehavior.setPeekHeight(bottomSheet.getHeight());
    bottomBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}

4

Przykład z BottomSheetDialogFragment. Działa doskonale.

Edit 4.09.2020: Zastąpiony amortyzowane setBottomSheetCallback()zaddBottomSheetCallback()

class FragMenuBDrawer : BottomSheetDialogFragment() {

    ...

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog

        dialog.setOnShowListener {
            val bottomSheet = (it as BottomSheetDialog).findViewById<View>(com.google.android.material.R.id.design_bottom_sheet) as FrameLayout?
            val behavior = BottomSheetBehavior.from(bottomSheet!!)
            behavior.state = BottomSheetBehavior.STATE_EXPANDED

            behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
                override fun onStateChanged(bottomSheet: View, newState: Int) {
                    if (newState == BottomSheetBehavior.STATE_DRAGGING) {
                        behavior.state = BottomSheetBehavior.STATE_EXPANDED
                    }
                }

                override fun onSlide(bottomSheet: View, slideOffset: Float) {}
            })
        }

        // Do something with your dialog like setContentView() or whatever
        return dialog
    }

    ...
}

3

Nie musisz blokować wszystkich wydarzeń, gdy dolny arkusz jest wyłączony. Możesz zablokować tylko wydarzenie ACTION_MOVE. Dlatego użyj takiego niestandardowego zachowania dolnego arkusza

public class BottomSheetBehaviorWithDisabledState<V extends View> extends BottomSheetBehavior<V> {
    private boolean enable = true;

    /**
     * Default constructor for instantiating BottomSheetBehaviors.
     */
    public BottomSheetBehaviorWithDisabledState() {
        super();
    }

    /**
     * Default constructor for inflating BottomSheetBehaviors from layout.
     *
     * @param context The {@link Context}.
     * @param attrs   The {@link AttributeSet}.
     */
    public BottomSheetBehaviorWithDisabledState(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public void setEnable(boolean enable){
        this.enable = enable;
    }

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
        if (!enable && event.getAction() == MotionEvent.ACTION_MOVE){
            return false;
        }
        return super.onInterceptTouchEvent(parent, child, event);
    }
}

Jak korzystasz z tej klasy? Otrzymuję wyjątek IllegalArgumentException: widok nie jest powiązany z BottomSheetBehavior
user3144836

3

Oto działająca wersja najlepszego rozwiązania w Kotlinie:

import android.support.design.widget.BottomSheetBehavior
import android.support.design.widget.CoordinatorLayout
import android.view.MotionEvent
import android.view.View

class CustomBottomSheetBehavior<V : View> : BottomSheetBehavior<V>() {

    @Suppress("UNCHECKED_CAST")
    companion object {
        fun <V : View> from(view: V): CustomBottomSheetBehavior<V> {
            val params = view.layoutParams as? CoordinatorLayout.LayoutParams ?:
                throw IllegalArgumentException("The view is not a child of CoordinatorLayout")
                params.behavior as? BottomSheetBehavior<V> ?:
                    throw IllegalArgumentException("The view is not associated with BottomSheetBehavior")
                params.behavior = CustomBottomSheetBehavior<V>()
            return params.behavior as CustomBottomSheetBehavior<V>
        }
    }

    override fun onInterceptTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean {
        return false
    }

    override fun onTouchEvent(parent: CoordinatorLayout, child: V, event: MotionEvent): Boolean {
        return false
    }

    override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
        return false
    }

    override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {}

    override fun onStopNestedScroll(coordinatorLayout: CoordinatorLayout, child: V, target: View, type: Int) {}

    override fun onNestedPreFling(coordinatorLayout: CoordinatorLayout, child: V, target: View, velocityX: Float, velocityY: Float): Boolean {
        return false
    }
}

Wtedy, kiedy chcesz użyć:

val bottomSheetBehavior by lazy {
    CustomBottomSheetBehavior.from(bottom_sheet_main)
}

To bottom_sheet_mainjest rzeczywisty widok przy użyciu rozszerzeń Kotlin dla Androida .


3

ustaw bottomSheet onClickListener na null.

bottomSheet.setOnClickListener(null);

ta linia wyłącza wszystkie działania dotyczące tylko arkusza dolnego i nie wpływa na widok wewnętrzny.


1
Powoduje to nieoczekiwaną animację przy próbie zamknięcia arkusza dolnego.
Adam Hurwitz

3
implementation 'com.google.android.material:material:1.2.0-alpha05'

możesz wyłączyć przeciąganie arkusza dolnego w ten sposób.

import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED

//another code

this.bottomSheetBehavior = BottomSheetBehavior.from(view)
this.bottomSheetBehavior.state = STATE_EXPANDED
this.bottomSheetBehavior.isDraggable = false // disable dragging

//another code
this.bottomSheetbehavior.isDraggable = true //draggable

(kotlin), mam nadzieję, że ta odpowiedź może rozwiązać twój problem.


1
Ta wersja alfa zachowuje się szaleńczo. Nie polecam :(
Adam Styrc

2

Znalazłem świetne rozwiązanie. Początkowy problem polegał na tym, że bottomSheet przechodził w stan HIDDEN, a następnie nie pojawiał się w bottomSheetDialog.show (). Ale chciałem, aby okno dialogowe było widoczne w metodzie show (), a także chciałem umożliwić użytkownikowi przesunięcie go w dół, aby wyglądało jak dolny arkusz. Poniżej przedstawiam to, co zrobiłem ...

    BottomSheetDialog itemTypeDialog = new BottomSheetDialog(this);
    View bottomSheetView = getLayoutInflater().inflate(R.layout.dialog_bottomsheet, null);
    itemTypeDialog.setContentView(bottomSheetView);
    BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from((View) bottomSheetView.getParent());
    bottomSheetBehavior.setBottomSheetCallback(bottomSheetCallback); // You can also attach the listener here itself.

    BottomSheetBehavior.BottomSheetCallback bottomSheetCallback =  new BottomSheetBehavior.BottomSheetCallback() {
    @Override
    public void onStateChanged(@NonNull View bottomSheet, int newState) {
        Log.d(TAG, "BottomSheetCallback: " + newState);
        if (newState == BottomSheetBehavior.STATE_HIDDEN) {
            bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
            itemTypeDialog.dismiss();
        } 
    }

    @Override
    public void onSlide(@NonNull View bottomSheet, float slideOffset) {

    }
};

ta jest doskonałą odpowiedzią
Vivek Kumar Srivastava,

2
  1. Skopiuj BottomSheetDialogdo swojego projektu i zmień nazwę naMyBottomSheetDialog
  2. dodać getBottomSheetBehaviordoMyBottomSheetDialog
  3. użyj MyBottomSheetDialogzamiast tegoBottomSheetDialog
  4. bottomSheetBehavior.setBottomSheetCallback

kod taki jak ten

public class MyBottomSheetDialog extends AppCompatDialog {

    // some code

    public BottomSheetBehavior<FrameLayout> getBottomSheetBehavior() {
        return mBehavior;
    }

    // more code

w swoim kodzie

    final BottomSheetBehavior<FrameLayout> bottomSheetBehavior = myBottomSheetDialog.getBottomSheetBehavior();
    bottomSheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
        @Override
        public void onStateChanged(@NonNull View bottomSheet, int newState) {
            if (newState == BottomSheetBehavior.STATE_DRAGGING) {
                bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        }

        @Override
        public void onSlide(@NonNull View bottomSheet, float slideOffset) {

        }

2

To jest w zasadzie wersja kotlin poprawnej odpowiedzi u góry:

    class LockedBottomSheetBehavior<V : View>(context: Context, attrs: AttributeSet) :
        BottomSheetBehavior<V>(context, attrs) {

    companion object {
        fun <V : View> from(view: V): LockedBottomSheetBehavior<*> {
            val params = view.layoutParams as? CoordinatorLayout.LayoutParams
                    ?: throw IllegalArgumentException("The view is not a child of CoordinatorLayout")
            return params.behavior as? LockedBottomSheetBehavior<*>
                    ?: throw IllegalArgumentException(
                            "The view is not associated with BottomSheetBehavior")
        }
    }

    override fun onInterceptTouchEvent(
            parent: CoordinatorLayout,
            child: V, event: MotionEvent
    ) = false

    override fun onTouchEvent(
            parent: CoordinatorLayout,
            child: V,
            event: MotionEvent
    ) = false

    override fun onStartNestedScroll(
            coordinatorLayout: CoordinatorLayout,
            child: V,
            directTargetChild: View,
            target: View,
            axes: Int,
            type: Int) = false

    override fun onNestedPreScroll(
            coordinatorLayout: CoordinatorLayout,
            child: V,
            target: View,
            dx: Int,
            dy: Int,
            consumed: IntArray,
            type: Int) {
    }

    override fun onStopNestedScroll(
            coordinatorLayout: CoordinatorLayout,
            child: V,
            target: View,
            type: Int) {
    }

    override fun onNestedPreFling(
            coordinatorLayout: CoordinatorLayout,
            child: V,
            target: View,
            velocityX: Float,
            velocityY: Float
    ) = false
}

Jak korzystasz z tej klasy? Otrzymuję wyjątek IllegalArgumentException: widok nie jest powiązany z BottomSheetBehavior
user3144836

1
app: layout_behavior = "UserLockBottomSheetBehavior"> w formacie XML, a następnie w kodzie wykonaj następujące czynności. // pobierz dolny widok arkusza LinearLayout llBottomSheet = (LinearLayout) findViewById (R.id.bottom_sheet); // zainicjuj zachowanie dolnego arkusza BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from (llBottomSheet);
Jamal

1

Spróbuj tego.

1) Utwórz dolny arkusz i zadeklaruj zmienną w swojej klasie java, takiej jak

private BottomSheetBehavior sheetBehavior;

2) sheetBehavior = BottomSheetBehavior.from(bottomSheet);

3) W funkcji wywołania zwrotnego w dolnej części arkusza dodaj następujące wiersze.

sheetBehavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
            @Override
            public void onStateChanged(@NonNull View bottomSheet, int newState) {
                switch (newState) {
                    case BottomSheetBehavior.STATE_HIDDEN:
                        Log.d(TAG, "--------------  STATE_HIDDEN");
                        break;
                    case BottomSheetBehavior.STATE_EXPANDED: {
                        Log.d(TAG, "--------------  STATE_EXPANDED");
                    }
                    break;
                    case BottomSheetBehavior.STATE_COLLAPSED: {
                        Log.d(TAG, "--------------  STATE_COLLAPSED");
                    }
                    break;
                    case BottomSheetBehavior.STATE_DRAGGING:
                        sheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                        break;
                    case BottomSheetBehavior.STATE_SETTLING:
                        Log.d(TAG, "--------------  STATE_SETTLING");
                        break;
                }
            }

            @Override
            public void onSlide(@NonNull View bottomSheet, float slideOffset) {

            }
        });

1

Na początku chcę tylko podziękować wszystkim, którzy próbowaliście udzielić odpowiedzi. Po prostu piszę tę odpowiedź, rozwiązując ten problem, jak chcę. Opiszę, jak robię to krok po kroku, korzystając z pomocy tutaj.

Wizualizacja: Po kliknięciu przycisku Show BottomSheetzobaczysz drugi ekran . Teraz zobaczysz, że BottomSheet jest po prostu zablokowany do przeciągania . Ale jeśli klikniesz na listę krajów, arkusz dolny zostanie ukryty . Taki był opis, teraz zajmijmy się Kodeksem.

  • Najpierw dodaj bibliotekę obsługi projektowania do pliku build.gradle :

    wdrożenie „com.android.support:design:28.0.0”

  • UserLockBottomSheetBehavior.java : Źródło : James Davis (dziękuję)

public class UserLockBottomSheetBehavior<V extends View> extends BottomSheetBehavior<V> {

    public UserLockBottomSheetBehavior() {
        super();
    }

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

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
        return false;
    }

    @Override
    public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
        return false;
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child, View directTargetChild, View target, int nestedScrollAxes) {
        return false;
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx, int dy, int[] consumed) {
    }

    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
    }

    @Override
    public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target, float velocityX, float velocityY) {
        return false;
    }

}
  • bottomsheet.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/bottomSheet"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center_vertical"
    android:orientation="vertical"
    app:behavior_hideable="true"
    app:layout_behavior="com.samsolution.custombottomsheet.UserLockBottomSheetBehavior">

 <RelativeLayout
     android:id="@+id/minimizeLayout"
     android:background="@color/colorPrimary"
     android:layout_width="match_parent"
     android:layout_height="?android:attr/actionBarSize">

     <TextView
         android:layout_centerHorizontal="true"
         android:padding="16dp"
         android:layout_width="wrap_content"
         android:layout_height="?android:attr/actionBarSize"
         android:gravity="center_horizontal|center"
         android:text="Country List"
         android:textColor="#FFFFFF"
         android:textStyle="bold" />
 </RelativeLayout>

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ListView
            android:id="@+id/homeCountryList"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </android.support.v7.widget.CardView>

</LinearLayout>
  • activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:layout_gravity="center"
        android:onClick="showCountryListFromBottomSheet">

        <Button
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_red_light"
            android:onClick="showCountryListFromBottomSheet"
            android:padding="16dp"
            android:text="Show BottomSheet"
            android:textAllCaps="false"
            android:textColor="#ffffff" />

    </LinearLayout>

    <include layout="@layout/bootomsheet" />

</android.support.design.widget.CoordinatorLayout>
  • MainActivity.java
public class MainActivity extends AppCompatActivity {

    private BottomSheetBehavior<LinearLayout> bottomSheetBehavior;                                  // BottomSheet Instance
    LinearLayout bottomsheetlayout;
    String[] list = {"A", "B", "C", "D", "A", "B", "C", "D","A", "B", "C", "D","A", "B", "C", "D","A", "B", "C", "D"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        bottomsheetlayout = findViewById(R.id.bottomSheet);
        bottomSheetBehavior = BottomSheetBehavior.from(bottomsheetlayout);

        ListView listView = findViewById(R.id.homeCountryList);
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this,android.R.layout.simple_list_item_1,list);
        listView.setAdapter(adapter);

        bottomSheetHide();                                                                          //BottomSheet get hide first time

        RelativeLayout minimizeLayoutIV;                                                            // It will hide the bottomSheet Layout
        minimizeLayoutIV = findViewById(R.id.minimizeLayout);
        minimizeLayoutIV.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               bottomSheetHide();
            }
        });
    }

    public void showCountryListFromBottomSheet(View view) {
        bottomSheetBehavior.setHideable(false);
        bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
    }

    public void bottomSheetHide(){
        bottomSheetBehavior.setHideable(true);
        bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
    }
}

Pierwszy ekran Drugi ekran


1

Rozwiązanie z zaakceptowanej odpowiedzi zadziałało w większości dla mnie, ale z jednym problemem: widoki, które znajdują się za dolnym widokiem arkusza, zaczęły reagować na zdarzenia dotykowe, jeśli zdarzenie dotykowe ma miejsce na obszarze dolnego arkusza, który jest wolny od widoków dziecka. Innymi słowy, jak widać na poniższym obrazku, gdy użytkownik wsunie palec w dolny arkusz, mapa zaczyna na niego reagować.

dolny obszar dotykowy

Aby rozwiązać problem, zmodyfikowałem onInterceptTouchEventmetodę, ustawiając touchListenerwidok dolnego arkusza (reszta kodu pozostaje taka sama jak w przyjętym rozwiązaniu).

override fun onInterceptTouchEvent(
        parent: CoordinatorLayout,
        child: V, event: MotionEvent
    ): Boolean {
        child.setOnTouchListener { v, event ->
            true
        }
        return false
    }

1

Z 'com.google.android.material:material:1.2.0-alpha06'

Działa świetnie z NestedScrollViewiRecyclerView

Przykładowy kod:

    LinearLayout contentLayout = findViewById(R.id.contentLayout);
    sheetBehavior = BottomSheetBehavior.from(contentLayout);
    sheetBehavior.setDraggable(false);

0

Dostosowanie peakHeightwartości zadziałało dla mnie.

Ustawiam peakheight jako wysokość spodu, jeśli jest rozszerzony.

    private val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {
    override fun onSlide(bottomSheet: View, slideOffset: Float) {

    }

    override fun onStateChanged(bottomSheet: View, newState: Int) {
        if (newState == BottomSheetBehavior.STATE_EXPANDED)
            bottomSheetBehavior.peekHeight = bottomSheet.height
    }
}

1
Nie jest to idealne rozwiązanie, ponieważ może powodować nieoczekiwane animacje.
Adam Hurwitz

W moim przypadku. Nie spowodowało to żadnych problemów z animacjami. Po prostu nie porusza się po rozwinięciu karty. Nie jest idealny, ale zadziałał zgodnie z oczekiwaniami!
pz64_

Ciekawe, że tak może być. Rozwiązałem problem z nieoczekiwanym zamknięciem mojego dolnego arkusza, ustawiając pasek narzędzi CollapsingToolbarLayout na niewidoczny lub zniknął, gdy dolny arkusz jest otwarty. Interakcja dotykowa związana z paskiem narzędzi, mimo że znajdował się pod spodem, powodowała nieoczekiwane zamknięcie arkusza dolnego. Problem został już rozwiązany.
Adam Hurwitz

0
    LayoutInflater inflater = LayoutInflater.from(context);
            View view = inflater.inflate(R.layout.bottomsheet_view_profile_image, null);
            BottomSheetDialog dialog = new BottomSheetDialog(context);
            dialog.setContentView(view);
            dialog.setCancelable(false);


            BottomSheetBehavior behavior = BottomSheetBehavior
                    .from(((View) view.getParent()));
            behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() {
                @Override
                public void onStateChanged(@NonNull View bottomSheet, int newState) {
                    if (newState == BottomSheetBehavior.STATE_DRAGGING) {
                        behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
                    }
                }

                @Override
                public void onSlide(@NonNull View bottomSheet, float slideOffset) {
                }
            });
            behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
            behavior.setSkipCollapsed(true);
            dialog.show();

0

to pierwszy wynik w google, więc uważam, że sprawiedliwe jest umieszczenie tutaj prostego rozwiązania:

   private fun disableDragToDismiss() {
    if (dialog is BottomSheetDialog) {
        val bsDialog = dialog as BottomSheetDialog
        bsDialog.behavior.isHideable = false
    } else {
        Log.d(TAG, " BottomSheetDialog with dialog that is not BottomSheetDialog")
    }
}

a nie tylko wywoływać to z onCreateView()poziomu BottomSheetDialogFragmentimplementacji


0

Mam ten sam problem BottomSheetDialogFragmenti stosuję wiele rozwiązań przy użyciu behaviorof, dialogale żadne z nich nie rozwiązuje mojego problemu, a następnie rozwiązałem go, ale ustawiając setCancelable(false);w momencie inicjalizacji dialog.

DialogEndRide dialogCompleteRide = new DialogEndRide();
dialogCompleteRide.setCancelable(false);
dialogCompleteRide.show(getChildFragmentManager(), "");

Spowoduje to wyłączenie gestu włączania BottomSheetDialogFragmenti możesz odrzucić dialogprogramowo za pomocą dismiss();funkcji.


-1

Miałem ten sam problem, rozwiązałem go kodem. Nie pozwoli użytkownikowi na przeciąganie arkusza dolnego. musisz obsługiwać stan programowo.

 mBottomSheetBehavior.isDraggable = false

-2

Po prostu użyj: bottomSheet.dismissOnDraggingDownSheet = false

Skopiowano ze strony Material.io:

let viewController: ViewController = ViewController() let bottomSheet: MDCBottomSheetController = MDCBottomSheetController(contentViewController: viewController)

// **** Ta linia zapobiega odrzuceniu przez przeciągnięcie dolnego arkusza ****

bottomSheet.dismissOnDraggingDownSheet = false

present(bottomSheet, animated: true, completion: nil)


to jest dla iOS, tutaj nie dla Androida
Back Packer
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.