问题
How do you set the state of a fragment extending BottomSheetDialogFragment
to expanded using BottomSheetBehavior#setState(STATE_EXPANDED)
using the Android Support Design Library (v23.2.1)?
https://code.google.com/p/android/issues/detail?id=202396 says:
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.
The suggested practice requires a view to be inflated first, but I\'m not sure how I\'ll set the BottomSheetBehaviour onto a fragment (BottomSheetDialogFragment
).
View bottomSheet = coordinatorLayout.findViewById(R.id.bottom_sheet);
BottomSheetBehavior behavior = BottomSheetBehavior.from(bottomSheet);
回答1:
"Note that you cannot call the method before view layouts."
The above text is the clue.
Dialogs have a listener that is fired once the dialog is shown. The dialog cannot be shown if it isn't layed out.
So, in the onCreateDialog()
of your modal bottom sheet (BottomSheetFragment
), just before returning the dialog (or anywhere, once you have a reference to the dialog), call:
// 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);
}
});
In my case, my custom BottomSheet
turned out to be:
@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;
}
}
Let me know if this helps.
UPDATE
Note that you can also override BottomSheetDialogFragment
as:
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;
}
}
But I really dont see why anyone would want to do that as the base BottomSheetFragment
doesn't do anything other than return a BottomSheetDialog
.
UPDATE FOR ANDROIDX
When using AndroidX, the resource previously found at android.support.design.R.id.design_bottom_sheet
can now be found at com.google.android.material.R.id.design_bottom_sheet
.
回答2:
efeturi's answer is great, however, if you want to use onCreateView() to create your BottomSheet, as opposed to going with onCreateDialog(), here is the code you will need to add under your onCreateView() method:
@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:
A simplistic and elegant solution:
BottomSheetDialogFragment
could be subclassed to address this:
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;
}
}
So extend this class instead of BottomSheetDialogFragment
to create your own bottom sheet.
Note
Change com.google.android.material.R.id.design_bottom_sheet
to android.support.design.R.id.design_bottom_sheet
if your project uses old Android support libraries.
回答4:
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);
}
});
I met NullPointException in BottomSheetBehavior.from(bottomSheet)
because d.findViewById(android.support.design.R.id.design_bottom_sheet)
returns null.
It's strange. I add this line of code to Watches in Android Monitor in DEBUG mode and found it return Framelayout normally.
Here's code of wrapInBottomSheet
in 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;
}
Occasionally, I found that R.id.design_bottom_sheet
is not equal to android.support.design.R.id.design_bottom_sheet
. They have different value in different R.java.
So I change android.support.design.R.id.design_bottom_sheet
to 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);
}
});
No more NullPointException now.
回答5:
Apply BottomsheetDialogFragment state in OnResume will solve this issue
@Override
public void onResume() {
super.onResume();
if(mBehavior!=null)
mBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
}
onShow(DialogInterface dialog) and postDelayed may cause animation glitch
回答6:
All results with using onShow() cause random render bug when soft keyboard is displayed. See screenshot bellow - BottomSheet dialog is not at bottom of screen but is placed like keyboard was displayed. This problem is not occurs always but quite often.
UPDATE
My solution with reflection of private member is unnecessary. Using postDelayed (with about 100 ms) for creating and showing dialog after hide soft keyboard is better solution. Then the above solutions with onShow() are 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);
So I implement other solution, but it requires using reflection, because BottomSheetDialog has all members as private. But it solves render bug. BottomSheetDialogFragment class is only AppCompatDialogFragment with onCreateDialog method which create BottomSheetDialog. I create own child of AppCompatDialogFragment which create my class extends BottomSheetDialog and which solves access to private behavior member and set it in onStart method to STATE_EXPANDED state.
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());
}
....
}
回答7:
I think those above is better. Sadly I did not find those solution before I had solved. But write my solution. quite similar to all.
==================================================================================
I face the same issue. This is what I solved. The Behavior is hidden in BottomSheetDialog, which is available to get the behavior If you would like not to change your parent layout to be CooridateLayout, you can try this.
STEP 1: customize the 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
}
}
STEP 2: make your fragment extend this customized 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()
}
}
回答8:
The easiest way I implemented is as below, Here we are finding android.support.design.R.id.design_bottom_sheet and setting bottom sheet state as EXPANDED.
Without this, my bottom sheet was always stuck in the COLLAPSED state if view height is more than 0.5 of screen height and I have to manually scroll to view full bottom 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
}
}
回答9:
Similar to uregentx answer, in kotlin, you can declare your fragment class that extends from BottomSheetDialogFragment
, and when the view is created you can set the dialog listener default state after the dialog is displayed.
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
}
}
Remember using material design implementation in gradle.
implementation "com.google.android.material:material:$version"
Also take a look to material design reference Bottom Sheets
来源:https://stackoverflow.com/questions/35937453/set-state-of-bottomsheetdialogfragment-to-expanded