Android "Лучшая практика" возвращает значения из диалога
Что такое "правильный" способ вернуть значения вызывающей активности из сложного пользовательского диалогового окна - например, текстовые поля, дату или время, кучу переключателей и т.д., плюс "Сохранить" и "Отменить" "?
Некоторые методы, которые я видел в Интернете, включают:
-
публичные элементы данных в классе, создаваемом диалогом, который может быть прочитан Activity
-
публичные "get" аксессоры., "..."., "
-
Запуск диалога с помощью Intent (в отличие от show()) плюс обработчики в классе Dialog, которые берут входные данные из различных элементов управления и связывают их для передачи обратно в Activity, поэтому, когда прослушиватель обращается к "Save" пакет передается с помощью ReturnIntent()
-
Слушатели в Управлении, которые обрабатывают ввод элементов управления, которые находятся в диалоговом окне, например, поэтому слушатели TimePicker или DatePicker действительно находятся в Activity. В этой схеме практически вся работа выполняется в Activity
-
Один прослушиватель в действии для кнопки "Сохранить", а затем "Активность" непосредственно допрашивает элементы управления в диалоговом окне; Действие отклоняет диалог.
... плюс еще, что я уже забыл.
Существует ли конкретный метод, который учитывал бы канонически правильный или "лучший метод"?
Ответы
Ответ 1
Я использую следующий путь:
- Все мои действия имеют одну и ту же родительскую активность (скажем, ControlActivity). ControlActivity имеет
private volatile Bundle controlBundle;
с соответствующим устройством getter/setter
-
Когда я начинаю диалог, я использовал диалог через мой собственный метод:
public void showMyDialog(int id, Bundle bundle)
{
this.controlBundle=bundle;
this.showDialog(id, bundle);
}
Поэтому каждый раз, когда я знаю параметры, отправленные в диалог
- Когда диалог вот-вот завершится, я создаю в диалоге еще один
Bundle
с необходимыми значениями, а затем поставлю их через набор Activity
:
((ControlActivity )this.getOwnerActivity).setControlBundle(bundle);
Итак, в конце, когда заканчивается диалог, я знаю значение "возвращено" из диалогового окна. Я знаю, что он не похож на int retCode=this.showMyDialog();
немного сложнее, но он работоспособен.
Ответ 2
Возможно, я неправильно понимаю ваш вопрос, но почему бы просто не использовать встроенную систему слушателей:
builder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// run whatever code you want to run here
// if you need to pass data back, just call a function in your
// activity and pass it some parameters
}
})
Вот как я всегда обрабатывал данные из диалоговых окон.
EDIT: Позвольте мне привести более конкретный пример, который лучше ответит на ваш вопрос. Я собираюсь украсть код с этой страницы, который вы должны прочитать:
http://developer.android.com/guide/topics/ui/dialogs.html
// Alert Dialog code (mostly copied from the Android docs
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Pick a color");
builder.setItems(items, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
myFunction(item);
}
});
AlertDialog alert = builder.create();
...
// Now elsewhere in your Activity class, you would have this function
private void myFunction(int result){
// Now the data has been "returned" (as pointed out, that not
// the right terminology)
}
Ответ 3
Для моего MIDI-приложения мне нужны диалоги подтверждения да/нет/отмена, поэтому я сначала создал обычный класс StandardDialog:
public class StandardDialog {
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Handler;
public class StandardDialog {
public static final int dlgResultOk = 0;
public static final int dlgResultYes = 1;
public static final int dlgResultNo = 2;
public static final int dlgResultCancel = 3;
public static final int dlgTypeOk = 10;
public static final int dlgTypeYesNo = 11;
public static final int dlgTypeYesNoCancel = 12;
private Handler mResponseHandler;
private AlertDialog.Builder mDialogBuilder;
private int mDialogId;
public StandardDialog(Activity parent,
Handler reponseHandler,
String title,
String message,
int dialogType,
int dialogId) {
mResponseHandler = reponseHandler;
mDialogId = dialogId;
mDialogBuilder = new AlertDialog.Builder(parent);
mDialogBuilder.setCancelable(false);
mDialogBuilder.setTitle(title);
mDialogBuilder.setIcon(android.R.drawable.ic_dialog_alert);
mDialogBuilder.setMessage(message);
switch (dialogType) {
case dlgTypeOk:
mDialogBuilder.setNeutralButton("Ok", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mResponseHandler.sendEmptyMessage(mDialogId + dlgResultOk);
}
});
break;
case dlgTypeYesNo:
case dlgTypeYesNoCancel:
mDialogBuilder.setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mResponseHandler.sendEmptyMessage(mDialogId + dlgResultYes);
}
});
mDialogBuilder.setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mResponseHandler.sendEmptyMessage(mDialogId + dlgResultNo);
}
});
if (dialogType == dlgTypeYesNoCancel) {
mDialogBuilder.setNeutralButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mResponseHandler.sendEmptyMessage(mDialogId + dlgResultCancel);
}
});
}
break;
}
mDialogBuilder.show();
}
}
Далее, в моей основной деятельности у меня уже был обработчик сообщений для обновлений пользовательского интерфейса из других потоков, поэтому я просто добавил код для обработки сообщений из диалогов. Используя другой параметр dialogId, когда я создаю экземпляр StandardDialog для различных программных функций, я могу выполнить правильный код для обработки ответов yes/no/cancel на разные вопросы. Эта идея может быть расширена для сложных пользовательских диалогов, отправив Bundle данных, хотя это намного медленнее, чем простое целое сообщение.
private Handler uiMsgHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg != null) {
// {Code to check for other UI messages here}
// Check for dialog box responses
if (msg.what == (clearDlgId + StandardDialog.dlgResultYes)) {
doClearDlgYesClicked();
}
else if (msg.what == (recordDlgId + StandardDialog.dlgResultYes)) {
doRecordDlgYesClicked();
}
else if (msg.what == (recordDlgId + StandardDialog.dlgResultNo)) {
doRecordDlgNoClicked();
}
}
}
};
Тогда все, что мне нужно сделать, это определить методы do {Whatever}() в действии. Чтобы вызвать диалог, в качестве примера у меня есть метод, реагирующий на кнопку "Clear записанные MIDI-события" и подтвердите его следующим образом:
public void onClearBtnClicked(View view) {
new StandardDialog(this, uiMsgHandler,
getResources().getString(R.string.dlgTitleClear),
getResources().getString(R.string.dlgMsgClear),
StandardDialog.dlgTypeYesNo, clearDlgId);
}
clearDlgId
определяется как уникальное целое в другом месте. Этот метод создает диалог "Да/Нет" перед активностью, который теряет фокус до тех пор, пока диалоговое окно не закрывается, и в это время активность получает сообщение с результатом диалога. Затем обработчик сообщения вызывает метод doClearDlgYesClicked()
, если нажата кнопка "Да". (Мне не понадобилось сообщение для кнопки "Нет", так как в этом случае не было никаких действий).
В любом случае, этот метод работает для меня и облегчает передачу результатов из диалогового окна.
Ответ 4
Я размышлял над этим сам на некоторое время, и в конечном итоге самый удобный способ, который я нашел, это разбить мою деятельность на различные методы, которые представляют каждую единицу управляющего потока. Например, если действия в моей деятельности говорят: загружать переменные из намерения, проверять некоторые данные, обрабатывать и продолжать, если они доступны, если не выполнять фоновый вызов, ждать взаимодействия с пользователем, начать другое действие.
Я обычно набираю части, которые являются общими, первые два и последние в этом случае. Я завершу первые в onCreate()
и сделаю отдельный для последнего... say startAnotherActivity(Data)
. Вы можете расположить средние части, чтобы они состояли из checkData(Data)
(возможно, слияния с onCreate()
), который вызывает либо processAvailableData(Data)
, либо performBackgroundTask(Data)
. Фоновая задача выполнит операцию в фоновом режиме и возвращает управление на onBackgroundTaskCompleted(OtherData)
.
Теперь оба processAvailableData(Data)
и onBackgroundTaskCompleted(OtherData)
вызывают метод getUserResponse()
, который, в свою очередь, может либо вызвать startAnotherActivity(Data)
, либо объединить свои функции с самим собой.
Я считаю, что этот подход дает ряд преимуществ.
- Это помогает с проблемой возврата данных, на которую указывает ваш вопрос, путем "перемещения вперед" вместо возврата данных.
- Это позволяет легко добавлять новые функции. Например, если мы хотим предоставить пользователю больше опций, мы можем просто вызвать соответствующий метод из
getUserResponse()
, который может повлиять на данные, которые в конечном итоге будут переданы следующему действию.
- Это помогает избежать ненужных проблем с потоком (ознакомьтесь с вопросами, относящимися к
finish()
и return
на SO), когда наше интуитивное предположение является определенным потоком и оказывается другим.
- Помогает лучше управлять переменными, поэтому у вас не будет много полей уровня класса, чтобы избежать проблем с доступом к переменным в анонимных внутренних классах (onClick(), doInBackground() и т.д.).
Я уверен, что больше методов добавляет некоторые накладные расходы, но, вероятно, компенсируется преимуществами потока, повторного использования и простоты, которые вы получаете (хотелось бы услышать мнение эксперта по компиляции на этом).
Ответ 5
Объясню одно решение на примере двух фрагментов. Представьте, что существует SimpleFragment
, который имеет только одно текстовое поле для отображения даты. Тогда существует DatePickerFragment
, который позволяет выбрать конкретную дату. Я хочу, чтобы DatePickerFragment
передавал значение даты обратно вызывающему SimpleFragment
всякий раз, когда пользователь подтверждает ее выбор.
SimpleFragment
Итак, в первую очередь мы начинаем DatePickerFragment
из SimpleFragment
:
private DateTime mFavoriteDate; // Joda-Time date
private void launchDatePicker() {
DatePickerFragment datePickerFragment = new DatePickerFragment();
Bundle extras = new Bundle();
// Pass an initial or the last value for the date picker
long dateInMilliSeconds = mFavoriteDate.getMillis();
extras.putLong(BundleKeys.LAST_KNOWN_DATE, dateInMilliSeconds);
datePickerFragment.setArguments(extras);
datePickerFragment.setTargetFragment(this, SIMPLE_FRAGMENT_REQUEST_CODE);
datePickerFragment.show(getActivity().getSupportFragmentManager(),
DatePickerFragment.FRAGMENT_TAG);
}
DatePickerFragment
В диалоговом фрагменте мы готовимся к возврату выбранной даты, когда пользователь нажимает положительную кнопку:
public static final String DATE_PICKED_INTENT_KEY = "DATE_PICKED_INTENT_KEY";
public static final int DATE_PICKED_RESULT_CODE = 123;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
// ...
Long dateInMilliSeconds = getArguments().getLong(BundleKeys.LAST_KNOWN_DATE);
DateTime date = new DateTime(dateInMilliSeconds);
initializePickerUiControl(date);
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(activity);
dialogBuilder
.setPositiveButton(R.string.date_picker_positive, (dialog, which) -> {
// Pass date to caller
passBackDate();
})
.setNegativeButton(R.string.date_picker_negative, (dialog, which) -> {
// Nothing to do here
});
return dialogBuilder.create();
}
private void passBackDate() {
DateTime dateTime = getDateTimeFromPickerControl();
Intent intent = new Intent();
intent.putExtra(DATE_PICKED_INTENT_KEY, dateTime.getMillis());
getTargetFragment().onActivityResult(
getTargetRequestCode(), DATE_PICKED_RESULT_CODE, intent);
}
SimpleFragment
В обратном фрагменте мы используем то, что было возвращено диалогом:
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == SIMPLE_FRAGMENT_REQUEST_CODE &&
resultCode == DatePickerFragment.DATE_PICKED_RESULT_CODE) {
long datePickedInMilliseconds = data.getLongExtra(
DatePickerFragment.DATE_PICKED_INTENT_KEY, 0);
mFavoriteDate = new DateTime(datePickedInMilliseconds);
updateFavoriteDateTextView();
}
else {
super.onActivityResult(requestCode, resultCode, data);
}
}
Ссылки на mattpic, которые ранее дали отличный ответ.
Ответ 6
После небольшого исследования я остановился на интерфейсе обратного вызова. Мой код выглядит следующим образом:
MyFragment.java
public class MyFragment extends Fragment {
...
private void displayFilter() {
FragmentManager fragmentManager = getFragmentManager();
FilterDialogFragment filterDialogFragment = new FilterDialogFragment();
Bundle bundle = new Bundle();
bundle.putSerializable("listener", new FilterDialogFragment.OnFilterClickListener() {
@Override
public void onFilterClickListener() {
System.out.println("LISTENER CLICKED");
}
});
filterDialogFragment.setArguments(bundle);
filterDialogFragment.show(fragmentManager, DIALOG_FILTER);
}
MyDialog.java
public class MyDialog extends DialogFragment {
private ImageButton mBtnTest;
private OnFilterClickListener mOnFilterClickListener;
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// Get the layout inflater
LayoutInflater inflater = getActivity().getLayoutInflater();
View filterLayout = inflater.inflate(R.layout.filter_dialog, null);
// Inflate and set the layout for the dialog
// Pass null as the parent view because its going in the dialog layout
builder.setView(filterLayout)
.setTitle("Filter");
Dialog dialog = builder.create();
mOnFilterClickListener = (OnFilterClickListener) getArguments().getSerializable("listener");
mBtnTest = (ImageButton)filterLayout.findViewById(R.id.fandb);
mBtnTest.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mOnFilterClickListener.onFilterClickListener();
dismiss();
}
});
return dialog;
}
public interface OnFilterClickListener extends Serializable {
void onFilterClickListener();
}
}