Установите состояние 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);  

Ответы:


221

«Обратите внимание, что вы не можете вызвать метод перед просмотром макетов».

Приведенный выше текст является подсказкой.

У диалогов есть слушатель, который запускается после показа диалога . Диалог не может быть показан, если он не выложен.

Итак, в 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не делает ничего, кроме возврата 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 08

21
При использовании AndroidX ресурс можно найти по адресуcom.google.android.material.R.id.design_bottom_sheet
importantx

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

@Stimoni 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.

Это странно. Я добавляю эту строку кода в Watches in Android Monitor в режиме 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
    }
}

Не забудьте использовать реализацию материального дизайна в градиенте.

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

Также ознакомьтесь со справочником по материальному дизайну Bottom Sheets


Откуда dialog?переменная в onCreateView?
Эрик Смит

dialogявляется свойством класса DialogFragment, фактически является геттером. В этом примере я использовал этот геттер для получения текущего экземпляра 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вы можете сделать то же самое, преобразовав диалог из этого DialogFragment в 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.