将 BottomSheetDialogFragment 的状态设置为展开

Set state of BottomSheetDialogFragment to expanded

如何使用 Android 支持设计库 (v23.2.1) 将扩展 BottomSheetDialogFragment 的片段状态设置为使用 BottomSheetBehavior#setState(STATE_EXPANDED) 扩展?

https://code.google.com/p/android/issues/detail?id=202396 说:

Bottom sheets are set to STATE_COLLAPSED at first. Call BottomSheetBehavior#setState(STATE_EXPANDED) if you want to expand it. Note that you cannot call the method before view layouts.

suggested practice 需要先膨胀视图,但我不确定如何将 BottomSheetBehaviour 设置到片段上 (BottomSheetDialogFragment)。

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

"Note that you cannot call the method before view layouts."

以上文字就是线索

对话框有一个侦听器,一旦对话框显示就会被触发。对话框不布局就无法显示

所以,在你的模式底部 sheet (BottomSheetFragment) 的 onCreateDialog() 中,就在 return 对话框之前(或任何地方,一旦你有参考对话框),调用:

// 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 除了 return a BottomSheetDialog.[=22= 什么都不做]

AndroidX更新

使用 AndroidX 时,以前在 android.support.design.R.id.design_bottom_sheet 找到的资源现在可以在 com.google.android.material.R.id.design_bottom_sheet 找到。

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

我在 BottomSheetBehavior.from(bottomSheet) 中遇到了 NullPointException 因为 d.findViewById(android.support.design.R.id.design_bottom_sheet) returns null.

很奇怪。我将这行代码添加到 Android Monitor in DEBUG 模式下的 Watches 中,发现 return Framelayout 正常。

这是 BottomSheetDialog 中 wrapInBottomSheet 的代码:

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。

显示软键盘时,使用 onShow() 的所有结果都会导致随机渲染错误。请参见下面的屏幕截图 - BottomSheet 对话框不在屏幕底部,而是像显示键盘一样放置。这个问题并不总是发生,但经常发生。

更新

我的私有成员反射的解决方案是不必要的。使用 postDelayed(大约 100 毫秒)在隐藏软键盘后创建和显示对话框是一个更好的解决方案。那么上面用onShow()的方案就ok了

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 class 只是具有创建 BottomSheetDialog 的 onCreateDialog 方法的 AppCompatDialogFragment。我创建自己的 AppCompatDialogFragment 子项,它创建我的 class extends 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());
    }

    ....
}

onResume 中应用 BottomsheetDialogFragment 状态将解决此问题

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

onShow(DialogInterface dialog)postDelayed可能会导致动画故障

一个简单而优雅的解决方案:

BottomSheetDialogFragment 可以子classed 来解决这个问题:

class NonCollapsableBottomSheetDialogFragment extends BottomSheetDialogFragment {

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

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

因此扩展此 class 而不是 BottomSheetDialogFragment 以创建您自己的底部 sheet。

备注

如果您的项目使用旧的 Android 支持库,请将 com.google.android.material.R.id.design_bottom_sheet 更改为 android.support.design.R.id.design_bottom_sheet

我实现的最简单的方法如下,在这里我们找到 android.support.design.R.id.design_bottom_sheet 并将底部 sheet 状态设置为 EXPANDED.

没有这个,我的底部 sheet 总是卡在折叠状态 如果视图高度超过屏幕高度的 0.5,我必须手动滚动才能查看全底 sheet.

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

我觉得上面那些比较好。 可悲的是,在我解决之前我没有找到那些解决方案。 但是写下我的解决方案。与所有人都非常相似。

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

我遇到了同样的问题。 这就是我解决的。 Behavior 隐藏在 BottomSheetDialog 中,可以通过它来获取 behavior 如果您不想将父布局更改为 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 = BottomSheetBehavior.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()
   }
}

类似于 uregentx 答案,在 kotlin 中,您可以声明从 [=11= 延伸的片段 class ]],创建视图时可以在显示对话框后设置对话框侦听器默认状态。

STATE_COLLAPSED: The bottom sheet is visible but only showing its peek height.

STATE_EXPANDED: The bottom sheet is visible and its maximum height.

STATE_HALF_EXPANDED: The bottom sheet is visible but only showing its half height.

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 中使用 material 设计实现。

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

另请参阅 material 设计参考 Bottom Sheets

我的回答与上面的大部分回答大致相同,只是稍作修改。与其使用 findViewById 首先找到底部 sheet 视图,我更倾向于不对任何框架视图资源 ID 进行硬编码,因为它们将来可能会发生变化。

setOnShowListener(dialog -> {
            BottomSheetBehavior bottomSheetBehavior = ((BottomSheetDialog)dialog).getBehavior();
            bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
        });
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return super.onCreateDialog(savedInstanceState).apply {
        setOnShowListener {
            (this@TipsBottomDialogFragment.dialog as BottomSheetDialog).behavior.setState(
                BottomSheetBehavior.STATE_EXPANDED
            )
        }
    }
}

在这里发布给未来的读者,因为我认为现在我们可以使用另一个解决方案。

我试图解决您用 BottomSheetDialog 描述的相同问题。

我不喜欢使用内部 Android ID,我刚刚发现 BottomSheetDialog getBehavior 中有一个方法可以使用:

您可以在 BottomSheetDialog:

中使用它

behavior.state = BottomSheetBehavior.STATE_EXPANDED

使用 BottomSheetDialogFragment,您可以将对话框从 DialogFragment 转换为 BottomSheetDialog

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
}

在您的 Kotlin BottomSheetDialogFragment class 中,如下重写 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
    }

在 Kotlin 中,在 BottomSheetDialogFragment

onStart() 中添加以下行
(dialog as BottomSheetDialog).behavior.state = BottomSheetBehavior.STATE_EXPANDED

根据下面 link 的回复,这对我有用。

behavior = BottomSheetBehavior.from(bottomSheet1);
if(action.equals("post") ) {
    behavior.setState(BottomSheetBehavior.STATE_EXPANDED);
    behavior.setDraggable(false); // disable dragging
}

enter link description here

您可以执行以下操作(Kotlin 版本):

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

    // rest of your stuff
}

这是一个非常简洁的 Kotlin 解决方案,效果很好。

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
    return (super.onCreateDialog(savedInstanceState) as BottomSheetDialog).apply {
        setOnShowListener {
           behavior.state = BottomSheetBehavior.STATE_EXPANDED
        }
    }
}

在 Kotlin 中扩展 BottomSheet 视图的简单解决方案:

(dialog as BottomSheetDialog).behavior.state = 
    BottomSheetBehavior.STATE_EXPANDED