29

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

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

Нижние листы устанавливаются STATE_COLLAPSED на первый. Вызов BottomSheetBehavior # setState (STATE_EXPANDED), если вы хотите его развернуть. Обратите внимание, что вы не можете вызвать метод перед макетами представления.

The suggested practice требует представления накачиваются первый, но я не знаю, как я установить BottomSheetBehaviour на фрагмент (BottomSheetDialogFragment).

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

ответ

70

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

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

Так, в 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; 
    } 
} 

Дайте мне знать, если это поможет.

UPDATE

Обратите внимание, что вы можете также переопределить 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

+0

Спасибо, я попробовал этот метод.Это заставляет «BottomSheetDialogFragment» казаться janky (кажется, пропускает кадры в анимации открытия), поскольку он идет от сложенного к расширенному поведению. Редактировать: Протестировано на устройствах Marshmallow и KitKat Android. – user2560886

+1

Он отлично работает для меня. Нет пропусков. Вы делаете что-нибудь еще, кроме как только возвращаете диалог? Поймите, если вы обновите свой пост с помощью своего кода, чтобы я мог получить лучшую идею. – efemoney

+0

Я не замечаю никакого удара; работает просто отлично. – rpattabi

8

ответ 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); 
} 
+1

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

+0

Это спасет мой день. Благодарю. – AndroidRuntimeException

1
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 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 сейчас.

0

Все результаты с использованием onShow() вызывают случайную ошибку рендеринга при отображении мягкой клавиатуры. См. Снимок экрана ниже - Диалог BottomSheet не находится внизу экрана, а помещен как клавиатура. Эта проблема возникает не всегда, а довольно часто.

enter image description here

UPDATE

Мое решение с отражением частного члена не требуется. Использование 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, который создает мой класс 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()); 
    } 

    .... 
} 
0

Нанести BottomsheetDialogFragment состояния в OnResume будет решить эту проблему

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

OnShow (диалог DialogInterface) и postDelayed может привести к анимационному глюку

0

Я написал подкласс BottomSheetDialogFragment справиться с этим:

public 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(android.support.design.R.id.design_bottom_sheet); 

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

}

Итак, расширяйте этот класс вместо BottomSheetDialogFragment, чтобы создать свой собственный нижний лист.

0

Самый простой способ я реализовал, как показано ниже, здесь мы находим 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 
    } 
}