Как обрабатывать изменения ориентации экрана при открытии диалога?

У меня есть приложение Android, которое уже обрабатывает изменения для ориентации, т.е. в манифесте android:configChanges="orientation" и обработчик onConfigurationChange() в действии, который переключается на соответствующий макет и создает его. У меня есть альбомная/портретная версия макета.

Проблема, с которой я сталкиваюсь, заключается в том, что у операции есть диалог, который может быть открыт, когда пользователь поворачивает ориентацию устройства. У меня также есть альбомная/портретная версия диалога.

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

Последний вариант блокировки приложения обращается ко мне, поскольку он сохраняет необходимость делать что-либо особенное в диалоговом окне. Я предполагаю, что я могу отключить ориентацию при открытии диалога, например

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);

а затем, когда он отклоняет

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);

Будет ли это разумным делом? Если ориентация экрана изменилась, когда она была заблокирована, будет ли она сразу ощущать изменение ориентации, когда оно было разблокировано?

Существуют ли альтернативы?

Ответ 1

Я бы рекомендовал не отключать вращение экрана, вместо этого дескриптора изменения конфигурации для диалогового окна. Для этого вы можете использовать один из этих двух способов:

Первый использует переменную флага в методе onSaveInstanceState (outState) и восстанавливает диалог onCreate (bundle):

в этом примере переменная моего флага называется "isShowing Dialog", когда метод onCreate вызывается системой Android в первый раз, аргумент bundle будет null, и ничего не произойдет. Однако, когда активность, которую он воссоздает с помощью изменения конфигурации (поворот экрана), пакет будет иметь логическое значение isShowing Dialog, ранее сохраненное методом inSaveInstanceState (...), поэтому, если переменная получает значение true, диалог создается снова, трюк здесь установлен флаг в true, когда диалог появляется, а false, когда он отсутствует, - это небольшой, но простой трюк.

Class MyClass extends Activity {
    Boolean isShowingDialog = false;
    AlertDialog myDialog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        if(savedInstanceState!=null){
            isShowingDialog = savedInstanceState.getBoolean("IS_SHOWING_DIALOG", false);
            if(isShowingDialog){
                createDialog();
            }
        }

    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putBoolean("IS_SHOWING_DIALOG", isShowingDialog);
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onPause() {
        if(myDialog!=null && myDialog.isShowing()) {
            myDialog.dismiss();
        }
    }

    private void createDialog() {
        AlertDialog.Builder dialog_builder = new AlertDialog.Builder(this);
        dialog_builder.setTitle("Some Title"):
        ... more dialog settings ...

        myDialog = dialog_builder.create();
        myDialog.show();
        isShowingDialog = true;
    }

    private void hideDialog(){
        myDialog.dismiss();
        isShowingDialog = false;
    }
}

Второй подход заключается в том, чтобы использовать способность компонентов фрагментов сохранять свои состояния, основная идея - создать диалог внутри фрагмента, возникает проблема отсоединения и повторной привязки фрагмента во время изменений конфигурации (потому что вам нужно убрать и показать диалог правильно), но решение очень похоже на первый подход. Преимущество этого подхода заключается в том, что если у вас есть AlertDialog с несколькими конфигурациями, когда фрагмент воссоздан, нет необходимости создавать и настраивать диалог еще раз, только сделать его show(), а состояние AlertDialog поддерживается фрагмент.

Надеюсь, это поможет.

Ответ 2

Я предлагаю, чтобы ваш диалог должен переопределить onSaveInstanceState() и onRestoreInstanceState(Bundle), чтобы сохранить его состояние в Bundle.

Затем вы переопределяете эти методы в своей деятельности, проверяете, отображается ли диалоговое окно, и если это так - вызов методов диалога для сохранения и восстановления состояния.

Если вы отобразите это диалоговое окно из фрагмента, вы захотите переопределить OnActivityCreated(Bundle) вместо OnRestoreInstanceState.

В качестве примера источника см. встроенное приложение часов, поставляемое с Android, где SetAlarm Activity обрабатывает TimePickerDialog таким образом.

Ответ 3

Если вы сами обрабатываете изменения ориентации, то вот подход.

Я не буду утверждать, что это изящное решение, но оно работает:

Вы можете отслеживать, имеет ли диалог активный экземпляр внутри самого класса диалога, используя статическую переменную activeInstance и переопределяя onStart(), чтобы установить activeInstance = this и onCancel(), чтобы установить activeInstance = null.

Предоставить статический метод updateConfigurationForAnyCurrentInstance(), который проверяет переменную activeInstance и, если не null, вызывает метод activeInstance.reInitializeDialog(), который является методом, который вы будете писать, чтобы содержать вызов setContentView() плюс код, который прокладывает обработчики для элементов управления диалогового окна (кнопка onClick handlers и т.д.) - это код, который обычно появляется в onCreate()). После этого вы должны восстановить любые отображаемые данные для этих элементов управления (из переменных-членов в объекте диалога). Например, если у вас есть список элементов для просмотра, и пользователь просматривал третий элемент этого списка перед изменением ориентации, вы повторно отобразили тот же элемент три в конце updateConfigurationForAnyCurrentInstance(), сразу после перезагрузка элементов управления из ресурса диалога и повторная проводка обработчиков управления.

Затем вы вызовете тот же метод reInitializeDialog() из onCreate(), сразу после super.onCreate() и поместите свой код инициализации onCreate() (например, настройте список элементов, из которых пользователь может выберите, как описано выше) после этого вызова.

Это приведет к тому, что загрузится новая ориентация (портретная или альбомная) для новой ориентации диалога (при условии, что у вас есть два ресурса, которые имеют одинаковое имя, одно в папке макета и другое в папке макета-земли, как обычно).

Вот некоторый код, который был бы в классе с именем YourDialog:

ArrayList<String> listOfPossibleChoices = null;
int currentUserChoice = 0;

static private YourDialog activeInstance = null;

@Override
protected void onStart() {
  super.onStart();
  activeInstance = this;
}

@Override
public void cancel() {
  super.cancel();
  activeInstance = null;
}


static public void updateConfigurationForAnyCurrentInstance() {
    if(activeInstance != null) {
        activeInstance.reInitializeDialog();
        displayCurrentUserChoice();
    }
}

private void reInitializeDialog() {
  setContentView(R.layout.your_dialog);
  btnClose = (Button) findViewById(R.id.btnClose);
  btnClose.setOnClickListener(this);
  btnNextChoice = (Button) findViewById(R.id.btnNextChoice);
  btnNextChoice.setOnClickListener(this);
  btnPriorChoice = (Button) findViewById(R.id.btnPriorChoice);
  btnPriorChoice.setOnClickListener(this);
  tvCurrentChoice = (TextView) findViewById(R.id.tvCurrentChoice);
}

private void displayCurrentUserChoice() {
  tvCurrentChoice.setText(listOfPossibleChoices.get(currentUserChoice));
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    reInitializeDialog();
    listOfPossibleChoices = new ArrayList<String>();
    listOfPossibleChoices.add("One");
    listOfPossibleChoices.add("Two");
    listOfPossibleChoices.add("Three");
    currentUserChoice = 0;
    displayCurrentUserChoice();
}

@Override
public void onClick(View v) {
    int viewID = v.getId();

    if(viewID == R.id.btnNextChoice) {
      if(currentUserChoice < (listOfPossibleChoices.size() - 1))
        currentUserChoice++;
        displayCurrentUserChoice();
      }
    }
    else if(viewID == R.id.btnPriorChoice) {
      if(currentUserChoice > 0) {
        currentUserChoice--;
        displayCurrentUserChoice();
      }
    }
    Etc.

Затем, в вашем основном действии onConfigurationChanged(), вы просто вызываете YourDialog.updateConfigurationForAnyCurrentInstance(), когда OSConfigurationChanged() вызывается ОС.