Ustaw stan BottomSheetDialogFragment na rozwinięty

92

Jak ustawić stan fragmentu rozszerzającego się BottomSheetDialogFragmentna rozszerzony przy BottomSheetBehavior#setState(STATE_EXPANDED)użyciu biblioteki projektów obsługi systemu Android (wersja 23.2.1)?

https://code.google.com/p/android/issues/detail?id=202396 mówi:

Dolne arkusze są początkowo ustawione na STATE_COLLAPSED. Wywołaj BottomSheetBehavior # setState (STATE_EXPANDED), jeśli chcesz go rozwinąć. Zauważ, że nie możesz wywołać metody przed przeglądaniem układów.

Zasugerował praktyka wymaga, aby być pierwszy zawyżone, ale nie jestem pewien, jak będę ustawić BottomSheetBehaviour na fragment ( BottomSheetDialogFragment).

View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);  
BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);  
user2560886
źródło

Odpowiedzi:

221

„Pamiętaj, że nie możesz wywołać metody przed wyświetlaniem układów”.

Powyższy tekst jest wskazówką.

Dialogi mają słuchacza, który jest zwolniony kiedy zostanie okno dialogowe pokazane . Nie można wyświetlić okna dialogowego, jeśli nie jest ustawione.

Tak więc w onCreateDialog()twoim modalnym arkuszu dolnym ( BottomSheetFragment), tuż przed zwróceniem okna dialogowego (lub gdziekolwiek, gdy masz odniesienie do okna dialogowego), wywołaj:

// This listener's onShow is fired when the dialog is shown
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
    @Override
    public void onShow(DialogInterface dialog) {

        // In a previous life I used this method to get handles to the positive and negative buttons
        // of a dialog in order to change their Typeface. Good ol' days.

        BottomSheetDialog d = (BottomSheetDialog) dialog;

        // This is gotten directly from the source of BottomSheetDialog
        // in the wrapInBottomSheet() method
        FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);

        // Right here!
        BottomSheetBehavior.from(bottomSheet)
            .setState(BottomSheetBehavior.STATE_EXPANDED);
    }
});

W moim przypadku mój zwyczaj BottomSheetokazał się:

@SuppressWarnings("ConstantConditions")
public class ShareBottomSheetFragment extends AppCompatDialogFragment {

    @NonNull @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        BottomSheetDialog dialog =
                new BottomSheetDialog(getActivity(), R.style.Haute_Dialog_ShareImage);

        dialog.setContentView(R.layout.dialog_share_image);

        dialog.findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dismiss();
            }
        });

        dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;

                FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);
                BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });

        SwitchCompat switchview = (SwitchCompat) dialog.findViewById(R.id.switchview);
        switchview.setTypeface(FontCache.get(dialog.getContext(), lookup(muli, NORMAL)));

        return dialog;
    }
}

Daj mi znać, jeśli to pomoże.

AKTUALIZACJA

Pamiętaj, że możesz również zastąpić BottomSheetDialogFragmentjako:

public class SimpleInitiallyExpandedBottomSheetFragment extends BottomSheetDialogFragment {

    @NonNull @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {

        BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);

        dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;

                FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);
                BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });

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

Ale naprawdę nie rozumiem, dlaczego ktokolwiek miałby chcieć to zrobić, ponieważ baza BottomSheetFragmentnie robi nic poza zwracaniem pliku BottomSheetDialog.

AKTUALIZACJA DLA ANDROIDX

Podczas korzystania z AndroidX zasób znaleziony wcześniej pod adresem android.support.design.R.id.design_bottom_sheetmożna teraz znaleźć pod adresem com.google.android.material.R.id.design_bottom_sheet.

efemoney
źródło
Dzięki, wypróbowałem tę metodę. Sprawia, że BottomSheetDialogFragmentwygląda to dziwnie (wydaje się, że pomija klatki w animacji początkowej) podczas przechodzenia od zwiniętego do rozwiniętego zachowania. Edycja: przetestowano to na urządzeniach z Androidem Marshmallow i KitKat
użytkownik2560886
1
U mnie działa idealnie. Bez przeskakiwania. Czy robisz coś innego oprócz zwracania tylko okna dialogowego? Będę wdzięczny, jeśli zaktualizujesz swój post kodem, abym mógł mieć lepszy pomysł.
efemoney
5
Czy to po prostu nie mogę znaleźć pakietu android.support.design.Rpo aktualizacji bibliotek wsparcia?
natario
2
Mam też problemy z rozwiązaniem android.support.design.R, tak jak @natario. Używam implementation "com.google.android.material:material:1.0.0". W projekcie używam również AndroidX.
hsson
21
W przypadku korzystania z systemu AndroidX zasób można znaleźć pod adresemcom.google.android.material.R.id.design_bottom_sheet
urgentx,
45

Odpowiedź efeturi jest świetna, jednak jeśli chcesz użyć onCreateView () do utworzenia arkusza BottomSheet, w przeciwieństwie do onCreateDialog () , oto kod, który musisz dodać pod swoją metodą onCreateView () :

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    getDialog().setOnShowListener(new DialogInterface.OnShowListener() {
        @Override
        public void onShow(DialogInterface dialog) {
            BottomSheetDialog d = (BottomSheetDialog) dialog;
            View bottomSheetInternal = d.findViewById(android.support.design.R.id.design_bottom_sheet);
            BottomSheetBehavior.from(bottomSheetInternal).setState(BottomSheetBehavior.STATE_EXPANDED);
        }
    });
    return inflater.inflate(R.layout.your_bottomsheet_content_layout, container, false);
}
goodKode
źródło
3
Alternatywnie nie musisz w ogóle wywoływać getDialog. Uważam, że najczystszym sposobem na to jest zastąpienie zarówno onCreateView, jak i onCreateDialog. Zbuduj swój widok w onCreateView (tak jak w przypadku dowolnego fragmentu) i wykonaj specyficzny dla okna kod w onCreateDialog (zadzwoń do super.onCreateDialog, aby uzyskać instancję)
Stimsoni
2
To ratuje mój dzień. Dzięki.
AndroidRuntimeException
@Stimsoni onCreateView nie jest wywoływana, jeśli jest używany onCreateDialog. developer.android.com/reference/android/support/v4/app/…
Vincent_Paing
1
@Vincent_Paing, tak jest. W załączonym linku jest napisane, że „onCreateView nie musi być implementowany”. Nie mówi, że nie zostanie wywołany. Spójrz na kod źródłowy tutaj github.com/material-components/material-components-android/blob/… . Domyślna implementacja wywołuje onCreateDialog, aby utworzyć dolny arkusz, a każde powyższe rozwiązanie nadal używa onCreateView, co oznacza, że ​​oba są zawsze wywoływane. Po prostu upewnij się, że nadal wywołujesz super.onCreateDialog (), jeśli go zastąpisz.
Stimsoni
w BottomSheetDialogFragment powoduje awarię w onCreateView () Umieściłem go w onViewCreated () i jest doskonały! dziękuję
avisper
22

Uproszczone i eleganckie rozwiązanie:

BottomSheetDialogFragment można podzielić na podklasy, aby rozwiązać ten problem:

class NonCollapsableBottomSheetDialogFragment extends BottomSheetDialogFragment {

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);

        bottomSheetDialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                FrameLayout bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet);

                BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
                behavior.setSkipCollapsed(true);
                behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });
        return bottomSheetDialog;
    }
}

Więc rozszerz tę klasę zamiast BottomSheetDialogFragmenttworzyć własny arkusz dolny.

Uwaga

Zmień com.google.android.material.R.id.design_bottom_sheetna, android.support.design.R.id.design_bottom_sheetjeśli projekt używa starych bibliotek obsługi Androida.

DYS
źródło
1
Wydaje się, że jest com.google.android.material.Rteraz zamiast android.support.design.R.
EpicPandaForce
@EpicPandaForce Pewnie. Zespół Androida w Google niedawno przepakował dawną bibliotekę wsparcia.
DYS
4

Myślę, że te powyżej są lepsze. Niestety nie znalazłem tych rozwiązań, zanim je rozwiązałem. Ale napisz moje rozwiązanie. całkiem podobny do wszystkich.

==================================================== ================================

Mam ten sam problem. To właśnie rozwiązałem. Zachowanie jest ukryte w oknie dialogowym BottomSheetDialog, które jest dostępne, aby uzyskać zachowanie Jeśli nie chcesz zmieniać układu nadrzędnego na CooridateLayout, możesz spróbować tego.

KROK 1: dostosuj BottomSheetDialogFragment

open class CBottomSheetDialogFragment : BottomSheetDialogFragment() {
   //wanna get the bottomSheetDialog
   protected lateinit var dialog : BottomSheetDialog 
   override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
      dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
      return dialog
   }

   //set the behavior here
   fun setFullScreen(){
      dialog.behavior.state = STATE_EXPANDED
   }
}

KROK 2: spraw, aby Twój fragment rozszerzył ten dostosowany fragment

class YourBottomSheetFragment : CBottomSheetDialogFragment(){

   //make sure invoke this method after view is built
   //such as after OnActivityCreated(savedInstanceState: Bundle?)
   override fun onStart() {
      super.onStart()
      setFullScreen()//initiated at onActivityCreated(), onStart()
   }
}
盧 誼 玲
źródło
3
dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;

                FrameLayout bottomSheet = (FrameLayout) d.findViewById(android.support.design.R.id.design_bottom_sheet);
                BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });

Spotkałem NullPointException w, BottomSheetBehavior.from(bottomSheet)ponieważ d.findViewById(android.support.design.R.id.design_bottom_sheet)zwraca null.

To dziwne. Dodałem ten wiersz kodu do zegarków w Android Monitor w trybie DEBUG i stwierdziłem, że normalnie zwraca Framelayout.

Oto kod wrapInBottomSheetw BottomSheetDialog:

private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
        final CoordinatorLayout coordinator = (CoordinatorLayout) View.inflate(getContext(),
                R.layout.design_bottom_sheet_dialog, null);
        if (layoutResId != 0 && view == null) {
            view = getLayoutInflater().inflate(layoutResId, coordinator, false);
        }
        FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
        BottomSheetBehavior.from(bottomSheet).setBottomSheetCallback(mBottomSheetCallback);
        if (params == null) {
            bottomSheet.addView(view);
        } else {
            bottomSheet.addView(view, params);
        }
        // We treat the CoordinatorLayout as outside the dialog though it is technically inside
        if (shouldWindowCloseOnTouchOutside()) {
            coordinator.findViewById(R.id.touch_outside).setOnClickListener(
                    new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            if (isShowing()) {
                                cancel();
                            }
                        }
                    });
        }
        return coordinator;
    }

Czasami stwierdzałem, że R.id.design_bottom_sheetto nie jest równe android.support.design.R.id.design_bottom_sheet. Mają różną wartość w różnych R.java.

Więc zmieniam android.support.design.R.id.design_bottom_sheetna R.id.design_bottom_sheet.

dialog.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                BottomSheetDialog d = (BottomSheetDialog) dialog;

                FrameLayout bottomSheet = (FrameLayout) d.findViewById(R.id.design_bottom_sheet); // use R.java of current project
                BottomSheetBehavior.from(bottomSheet).setState(BottomSheetBehavior.STATE_EXPANDED);
            }
        });

Koniec z wyjątkami NullPointException.

legendmohe
źródło
3

Zastosuj BottomsheetDialogFragmentstan w onResumerozwiąże ten problem

@Override
public void onResume() {
    super.onResume();
    if(mBehavior!=null)
       mBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}

onShow(DialogInterface dialog)i postDelayedmoże powodować usterki animacji

John Ruban Singh
źródło
2

Wszystkie wyniki przy użyciu onShow () powodują losowy błąd renderowania, gdy wyświetlana jest klawiatura programowa. Zobacz zrzut ekranu poniżej - okno dialogowe Arkusz dolny nie znajduje się na dole ekranu, ale jest umieszczone tak, jak została wyświetlona klawiatura. Ten problem nie występuje zawsze, ale dość często.

wprowadź opis obrazu tutaj

AKTUALIZACJA

Moje rozwiązanie z odbiciem członka prywatnego jest niepotrzebne. Lepszym rozwiązaniem jest użycie postDelayed (z około 100 ms) do tworzenia i wyświetlania okna dialogowego po ukryciu miękkiej klawiatury. Wtedy powyższe rozwiązania z onShow () są w porządku.

Utils.hideSoftKeyboard(this);
mView.postDelayed(new Runnable() {
    @Override
    public void run() {
        MyBottomSheetDialog dialog = new MyBottomSheetDialog();
        dialog.setListener(MyActivity.this);
        dialog.show(getSupportFragmentManager(), TAG_BOTTOM_SHEET_DLG);
    }
}, 100);

Wdrażam więc inne rozwiązanie, ale wymaga to użycia refleksji, ponieważ BottomSheetDialog ma wszystkie składowe jako prywatne. Ale rozwiązuje błąd renderowania. BottomSheetDialogFragment to tylko klasa AppCompatDialogFragment z metodą onCreateDialog, która tworzy BottomSheetDialog. Tworzę własne dziecko AppCompatDialogFragment, które tworzy moją klasę rozszerza BottomSheetDialog i rozwiązuje dostęp do prywatnego elementu zachowania i ustawiam go w metodzie onStart na stan STATE_EXPANDED.

public class ExpandedBottomSheetDialog extends BottomSheetDialog {

    protected BottomSheetBehavior<FrameLayout> mBehavior;

    public ExpandedBottomSheetDialog(@NonNull Context context, @StyleRes int theme) {
        super(context, theme);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        try {
            Field privateField = BottomSheetDialog.class.getDeclaredField("mBehavior");
            privateField.setAccessible(true);
            mBehavior = (BottomSheetBehavior<FrameLayout>) privateField.get(this);
        } catch (NoSuchFieldException e) {
            // do nothing
        } catch (IllegalAccessException e) {
            // do nothing
        }
    }

    @Override
    protected void onStart() {
        super.onStart();
        if (mBehavior != null) {
            mBehavior.setSkipCollapsed(true);
            mBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        }
    }
}


public class AddAttachmentBottomSheetDialog extends AppCompatDialogFragment {

    ....

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        return new ExpandedBottomSheetDialog(getContext(), getTheme());
    }

    ....
}
Petr Daňa
źródło
1

Najłatwiejszy sposób, który zaimplementowałem, jest taki, jak poniżej, tutaj znajdujemy android.support.design.R.id.design_bottom_sheet i ustawiamy stan dolnego arkusza jako EXPANDED .

Bez tego mój dolny arkusz zawsze był w stanie ZWINIĘTY, jeśli wysokość widoku jest większa niż 0,5 wysokości ekranu i muszę ręcznie przewijać, aby wyświetlić pełny dolny arkusz.

class BottomSheetDialogExpanded(context: Context) : BottomSheetDialog(context) {

    private lateinit var mBehavior: BottomSheetBehavior<FrameLayout>

    override fun setContentView(view: View) {
        super.setContentView(view)
        val bottomSheet = window.decorView.findViewById<View>(android.support.design.R.id.design_bottom_sheet) as FrameLayout
        mBehavior = BottomSheetBehavior.from(bottomSheet)
        mBehavior.state = BottomSheetBehavior.STATE_EXPANDED
    }

    override fun onStart() {
        super.onStart()
        mBehavior.state = BottomSheetBehavior.STATE_EXPANDED
    }
}
Achil
źródło
1

Podobnie jak odpowiedź uregentx , w kotlin możesz zadeklarować klasę fragmentu, która rozszerza się z BottomSheetDialogFragment, a kiedy widok jest tworzony, możesz ustawić domyślny stan detektora okna dialogowego po wyświetleniu okna dialogowego.

STATE_COLLAPSED: dolny arkusz jest widoczny, ale pokazuje tylko wysokość podglądu.

STATE_EXPANDED: dolny arkusz jest widoczny i jego maksymalna wysokość.

STATE_HALF_EXPANDED: dolny arkusz jest widoczny, ale pokazuje tylko połowę wysokości.

class FragmentCreateGroup : BottomSheetDialogFragment() {
      ...

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {
        // Set dialog initial state when shown
        dialog?.setOnShowListener {
            val bottomSheetDialog = it as BottomSheetDialog
            val sheetInternal: View = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet)!!
            BottomSheetBehavior.from(sheetInternal).state = BottomSheetBehavior.STATE_COLLAPSED
        }

        val view = inflater.inflate(R.layout.fragment_create_group, container, false)
        ...

        return view
    }
}

Pamiętaj o implementacji Material Design w Gradle.

implementation "com.google.android.material:material:$version"

Zapoznaj się również z materiałami referencyjnymi na dole strony

FJCG
źródło
Skąd dialog?pochodzi zmienna w onCreateView?
Eric Smith
dialogjest właściwością klasy DialogFragment, w rzeczywistości jest Getter. W tym przykładzie użyłem tego gettera, aby pobrać bieżącą instancję DialogFragment i setOnShowListenerdo niej. Być actionBarmoże actionBar?.subtitle = "abcd"
używałeś
1

Moja odpowiedź jest mniej więcej taka sama, jak większość powyższych odpowiedzi, z niewielką modyfikacją. Zamiast używać findViewById, aby najpierw znaleźć dolny widok arkusza, wolałem nie kodować na stałe identyfikatorów zasobów widoku struktury, ponieważ mogą się one zmienić w przyszłości.

setOnShowListener(dialog -> {
            BottomSheetBehavior bottomSheetBehavior = ((BottomSheetDialog)dialog).getBehavior();
            bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        });
prateek
źródło
1
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return super.onCreateDialog(savedInstanceState).apply {
        setOnShowListener {
            (this@TipsBottomDialogFragment.dialog as BottomSheetDialog).behavior.setState(
                BottomSheetBehavior.STATE_EXPANDED
            )
        }
    }
}
leigo
źródło
1

Wysyłając to tutaj przyszłym czytelnikom, ponieważ myślę, że możemy użyć innego rozwiązania.

Próbowałem rozwiązać ten sam problem, który opisałeś za pomocą pliku BottomSheetDialog.

Nie lubię używać wewnętrznych identyfikatorów Androida i właśnie odkryłem, że istnieje metoda BottomSheetDialog getBehavior, której możesz użyć:

Możesz użyć tego w swoim BottomSheetDialog:

behavior.state = BottomSheetBehavior.STATE_EXPANDED

Za pomocą BottomSheetDialogFragmentyou możesz zrobić to samo, rzutując okno dialogowe z tego DialogFragment do BottomSheetDialog.

culebrins
źródło
1

BottomSheetDialogFragment :

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    (dialog as? BottomSheetDialog)?.behavior?.state = STATE_EXPANDED
}

lub gdy jesteś gotowy do pokazania:

private fun onContentLoaded(items: List<Any>) {
    adapter.submitList(items)
    (dialog as? BottomSheetDialog)?.behavior?.state = STATE_EXPANDED
}
Yeldar.N
źródło
0

W klasie Kotlin BottomSheetDialogFragment nadpisz onCreateDialog jak poniżej

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val bottomSheetDialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
        bottomSheetDialog.setOnShowListener {
            val bottomSheet =
                bottomSheetDialog.findViewById<FrameLayout>(
                    com.google.android.material.R.id.design_bottom_sheet
                )
            val behavior = BottomSheetBehavior.from(bottomSheet!!)
            behavior.skipCollapsed = true
            behavior.state = BottomSheetBehavior.STATE_EXPANDED
        }
        return bottomSheetDialog
    }
Emi Raz
źródło