Встановіть стан BottomSheetDialogFragment як розширений


92

Як встановити стан фрагмента, що розширюється, BottomSheetDialogFragmentдо розширеного за BottomSheetBehavior#setState(STATE_EXPANDED)допомогою бібліотеки дизайну підтримки Android (v23.2.1)?

https://code.google.com/p/android/issues/detail?id=202396 говорить:

Спочатку для нижнього аркуша встановлено значення STATE_COLLAPSED. Зателефонуйте BottomSheetBehavior # setState (STATE_EXPANDED), якщо ви хочете його розширити. Зверніть увагу, що ви не можете викликати метод перед макетами подання.

Запропонував практик вимагає уявлень накачуються першим, але я не знаю , як я встановити BottomSheetBehaviour на фрагмент ( BottomSheetDialogFragment).

View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);  
BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);  

Відповіді:


222

"Зверніть увагу, що ви не можете викликати метод перед макетами подання."

Наведений текст є підказкою.

Діалоги мають прослуховувач, який запускається, коли діалогове вікно відображається . Діалогове вікно неможливо показати, якщо воно не розкладене.

Отже, у onCreateDialog()вашому модальному нижньому аркуші ( BottomSheetFragment), безпосередньо перед поверненням діалогового вікна (або де-небудь, коли у вас є посилання на діалогове вікно), зателефонуйте:

// 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);
    }
});

У моєму випадку моїм звичаєм BottomSheetвиявилося:

@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;
    }
}

Повідомте мене, якщо це допоможе.

ОНОВЛЕННЯ

Зверніть увагу, що ви також можете замінити BottomSheetDialogFragmentяк:

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;
    }
}

Але я справді не розумію, чому хтось хотів би це зробити, оскільки база BottomSheetFragmentне робить нічого, крім повернення a BottomSheetDialog.

ОНОВЛЕННЯ ДЛЯ ANDROIDX

При використанні AndroidX ресурс, який раніше знаходився за адресою, android.support.design.R.id.design_bottom_sheetтепер можна знайти за адресою com.google.android.material.R.id.design_bottom_sheet.


Дякую, я спробував цей метод. Це робить BottomSheetDialogFragmentвигляд незграбним (здається, пропускає кадри в початковій анімації), коли він переходить від згорнутого до розширеного. Редагувати: Тестував це на пристроях Android Marshmallow та KitKat
user2560886

1
Для мене це чудово працює. Без пропуску. Ви робите щось ще, крім просто повернення діалогового вікна? Будемо вдячні, якщо ви оновите свою публікацію своїм кодом, щоб я мав кращу ідею.
efemoney

5
Це просто я не можу знайти пакет android.support.design.Rпісля оновлення бібліотек підтримки?
Натаріо,

2
У мене також виникають проблеми з вирішенням проблем android.support.design.R, як і @natario. Я використовую implementation "com.google.android.material:material:1.0.0". Я також використовую AndroidX у проекті.
hsson

21
При використанні AndroidX ресурс можна знайти за адресоюcom.google.android.material.R.id.design_bottom_sheet
urgentx

45

Відповідь efeturi є чудовою, однак, якщо ви хочете використовувати onCreateView () для створення вашого BottomSheet, на відміну від onCreateDialog () , ось код, який вам потрібно буде додати під методом 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);
}

3
Крім того, вам зовсім не потрібно дзвонити в getDialog. Я вважаю, що найчистіший спосіб зробити це - перевизначити як onCreateView, так і onCreateDialog. Створіть свій вигляд у onCreateView (як і для будь-якого фрагмента) і виконайте специфічний код діалогу в onCreateDialog (зателефонуйте super.onCreateDialog, щоб отримати екземпляр)
Стімсоні,

2
Це врятує мій день. Дякую.
AndroidRuntimeException

@Stimsoni onCreateView не викликається, якщо використовується onCreateDialog. developer.android.com/reference/android/support/v4/app/…
Vincent_Paing

1
@Vincent_Paing, так. У прикріпленому посиланні написано "onCreateView не потрібно реалізовувати". Не сказано, що його не викликатимуть. Погляньте на вихідний код тут github.com/material-components/material-components-android/blob/… . Реалізація за замовчуванням вимагає onCreateDialog для створення нижнього аркуша, і кожне наведене вище рішення все ще використовує onCreateView, що означає, що їх обох завжди викликають. Просто переконайтеся, що ви все ще викликаєте super.onCreateDialog (), якщо все-таки перевизначите його.
Стімсоні,

у BottomSheetDialogFragment він розбиває мене в onCreateView () Я поміщаю його в onViewCreated (), і це чудово! дякую
avisper

22

Спрощене та елегантне рішення:

BottomSheetDialogFragment може бути підкласом для вирішення цього:

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;
    }
}

Тож розширюйте цей клас замість того, BottomSheetDialogFragmentщоб створювати власний нижній аркуш.

Примітка

Зміна com.google.android.material.R.id.design_bottom_sheetдо , android.support.design.R.id.design_bottom_sheetякщо ваш проект використовує старі бібліотеки Android підтримки.


1
Здається, com.google.android.material.Rзараз замість android.support.design.R.
EpicPandaForce

@EpicPandaForce Звичайно. Команда Android у Google нещодавно перепакувала колишню бібліотеку підтримки.
DYS

4

Я думаю, що це вище, краще. На жаль, я не знайшов цього рішення до того, як вирішив. Але напишіть моє рішення. цілком схожий на всіх.

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

Я стикаюся з тим самим питанням. Це я вирішив. Поведінка прихована в BottomSheetDialog, який доступний для отримання поведінки. Якщо ви не хочете змінювати батьківський макет на CooridateLayout, ви можете спробувати це.

КРОК 1: налаштуйте 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
   }
}

КРОК 2: змусіть ваш фрагмент розширити цей індивідуальний фрагмент

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()
   }
}

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);
            }
        });

Я зустрів NullPointException в BottomSheetBehavior.from(bottomSheet)тому, що d.findViewById(android.support.design.R.id.design_bottom_sheet)повертає null.

Це дивно. Я додав цей рядок коду до годинників на моніторі Android у режимі DEBUG і виявив, що він нормально повертає Framelayout.

Ось код wrapInBottomSheetу 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;
    }

Іноді я виявляв, що R.id.design_bottom_sheetне дорівнює android.support.design.R.id.design_bottom_sheet. Вони мають різне значення в різних R.java.

Тож я переходжу android.support.design.R.id.design_bottom_sheetна 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);
            }
        });

Зараз більше немає NullPointException.


3

Застосувати BottomsheetDialogFragmentдержаву в onResumeвирішить цю проблему

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

onShow(DialogInterface dialog)і postDelayedможе спричинити збої в анімації


2

Усі результати з використанням onShow () викликають випадкову помилку візуалізації, коли відображається програмна клавіатура. Дивіться знімок екрана нижче - діалогове вікно BottomSheet знаходиться не внизу екрана, а розміщується так, як відображалася клавіатура. Ця проблема виникає не завжди, але досить часто.

введіть тут опис зображення

ОНОВЛЕННЯ

Моє рішення з роздумами приватного члена непотрібне. Кращим рішенням є використання postDelayed (приблизно 100 мс) для створення та показу діалогового вікна після приховування програмної клавіатури. Тоді наведені вище рішення з onShow () є нормальними.

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);

Тому я реалізую інше рішення, але воно вимагає використання рефлексії, оскільки BottomSheetDialog має всіх членів як приватних. Але це вирішує помилку рендерингу. Клас BottomSheetDialogFragment - це лише AppCompatDialogFragment з методом onCreateDialog, який створює BottomSheetDialog. Я створюю власний нащадок AppCompatDialogFragment, який створює мій клас, розширює BottomSheetDialog і який вирішує доступ до приватного члена поведінки та встановлює його в методі onStart на стан 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());
    }

    ....
}

1

Найпростіший спосіб, який я реалізував, - як показано нижче, тут ми знаходимо android.support.design.R.id.design_bottom_sheet та встановлюємо стан нижнього аркуша як EXPANDED .

Без цього мій нижній аркуш завжди застряг у стані COLLAPSED, якщо висота подання перевищує 0,5 висоти екрану, і мені доводиться прокручувати вручну, щоб переглянути повний нижній аркуш.

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
    }
}

1

Подібно до відповіді uregentx , у kotlin ви можете оголосити свій клас фрагментів, що поширюється з BottomSheetDialogFragment, і коли створюється подання, ви можете встановити стан за замовчуванням для слухача діалогового вікна після відображення діалогового вікна.

STATE_COLLAPSED: Нижній аркуш видно, але відображається лише його висота.

STATE_EXPANDED: Видно нижній аркуш та його максимальна висота.

STATE_HALF_EXPANDED: Нижній аркуш видно, але відображається лише його половина висоти.

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
    }
}

Пам’ятайте, як використовували реалізацію дизайну матеріалів у gradle.

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

Також зверніть увагу на довідкові матеріали з дизайну матеріалів внизу


Звідки береться dialog?змінна в onCreateView?
Eric Smith

dialogє властивістю класу DialogFragment, насправді є Getter. У цьому прикладі я використовував цей геттер, щоб отримати поточний екземпляр DialogFragment і setOnShowListenerдо нього. Можливо, ви вже використовували такі вказівки у своєму проекті, наприклад, в дії, для доступу до панелі дій actionBarвикористовується геттер, тому ви можете змінити цей компонент, наприкладactionBar?.subtitle = "abcd"
FJCG

1

Моя відповідь більш-менш така ж, як і більшість наведених вище відповідей, з невеликими змінами. Замість того, щоб використовувати findViewById для першого пошуку подання нижнього аркуша, я вважав за краще не кодувати жодні ідентифікатори ресурсів подання фреймворка, оскільки вони можуть змінитися в майбутньому.

setOnShowListener(dialog -> {
            BottomSheetBehavior bottomSheetBehavior = ((BottomSheetDialog)dialog).getBehavior();
            bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        });

1
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return super.onCreateDialog(savedInstanceState).apply {
        setOnShowListener {
            (this@TipsBottomDialogFragment.dialog as BottomSheetDialog).behavior.setState(
                BottomSheetBehavior.STATE_EXPANDED
            )
        }
    }
}

1

Опублікувавши це тут для майбутніх читачів, оскільки, на мою думку, ми можемо використати інше рішення.

Я намагався вирішити ту саму проблему, яку ви описали за допомогою BottomSheetDialog.

Мені не подобається використовувати внутрішні ідентифікатори Android, і я щойно виявив, що всередині є метод, BottomSheetDialog getBehaviorякий ти можеш використовувати:

Ви можете використовувати це всередині вашого BottomSheetDialog:

behavior.state = BottomSheetBehavior.STATE_EXPANDED

За допомогою BottomSheetDialogFragmentви можете зробити те ж саме, що і діалог з цього діалогового фрагмента BottomSheetDialog.


1

BottomSheetDialogFragment :

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

або коли готовий показати:

private fun onContentLoaded(items: List<Any>) {
    adapter.submitList(items)
    (dialog as? BottomSheetDialog)?.behavior?.state = STATE_EXPANDED
}

0

У своєму класі Kotlin BottomSheetDialogFragment перевизначте onCreateDialog, як показано нижче

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
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.