如何让 DatePickerDialog 在更改屏幕方向时保持选定的用户日期?
How to make DatePickerDialog maintaining selected user date when changing screen orientation?
在打开的 DatePickerDialog 中,当屏幕方向改变时,它会重置所选的用户数据。
(DatePickerDialog 不会关闭并且不会维护所选数据)
代码:
public class ActivityNeki extends FragmentActivity {
DialogFragment newDF = null;
private int datY, datM, datD;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
if(savedInstanceState == null){ setTheData(); writeTheData(); }
}
@Override protected void onSaveInstanceState(Bundle outState) {
outState.putInt("izY", datY); outState.putInt("izM", datM); outState.putInt("izD", datD);
super.onSaveInstanceState(outState);
}
@Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState);
datY = savedInstanceState.getInt("izY"); datM = savedInstanceState.getInt("izM"); datD = savedInstanceState.getInt("izD");
writeTheData();
}
public void onClickOpenDPD(View view) { // the method that is caled from XML onClick
class MyDialogFragment extends DialogFragment {
@Override public void onDestroyView() {
if (getDialog() != null && getRetainInstance()) getDialog().setDismissMessage(null);
super.onDestroyView();
}
@Override public void onCreate(Bundle state) { super.onCreate(state);
setRetainInstance(true);
}
@Override public Dialog onCreateDialog(Bundle state) {
DatePickerDialog dpd = new DatePickerDialog( getActivity(), new DatePickerDialog.OnDateSetListener() {
@Override public void onDateSet(DatePicker view, int leto, int mesec, int dan) {
datY = leto; datM = mesec; datD = dan;
writeTheData();
} }, datY, datM, datD);
return dpd;
}
}
newDF = new MyDialogFragment();
newDF.show( getSupportFragmentManager(), null );
}
public void setTheData(){
Calendar c = Calendar.getInstance();
datY = c.get(Calendar.YEAR); datM = c.get(Calendar.MONTH); datD = c.get(Calendar.DAY_OF_MONTH);
}
public void writeTheData(){ /* writes the data in a txtView */ }
}
建议我,如何解决这个问题?
当方向改变时,应用程序实际上被销毁并重新创建。所以你必须在某处保存状态,例如在 instantState Bundle
-- 新编辑
我尝试了一种不同的方法,使用 Button 中的 OnClickListener 回调,并保留了改变方向的状态。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// the method that is called from XML onClick
class MyDialogFragment extends DialogFragment {
@Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
setRetainInstance(true);
}
@Override
public Dialog onCreateDialog(Bundle state) {
DatePickerDialog dpd = new DatePickerDialog(getActivity(), new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int leto, int mesec, int dan) {
datY = leto;
datM = mesec;
datD = dan;
writeTheData();
}
}, datY, datM, datD);
return dpd;
}
}
newDF = new MyDialogFragment();
newDF.show(getFragmentManager(), null);
}
});
}
你的代码看起来很糟糕。
首先,您在方法主体中声明匿名片段 class - 但是,最好将此 class 与 Activity
class 分开或将其设为静态内部 class.
然后,你setRetainInstance(true)
,告诉Android不要在Activity旋转时破坏片段。这适用于其他用例,您的情况并非如此。
我建议您阅读以下信息:
How to communicate Activity with Fragments
基本上,如果您按照 Android 规则(见上面的链接)进行游戏,大部分用户输入将在设备旋转时立即保存。
正确实现的 DialogFragment 将能够恢复状态,而无需您进行任何额外的工作。您代码中的主要问题是非静态内部 class。这是有缺陷的,原因有两个:
一个非静态内部 class 自然地保持对其外部 class 的引用(参见 #2 中的 link)。这实际上是对您的 activity、您的上下文和观点的引用;基本上是用户所面对的一切。当设备旋转或用户切换到另一个应用程序时,您的应用程序很可能会关闭。但是,保留的片段将保持活动状态,这有时非常有用。但是因为在你的情况下它是一个内部 class 垃圾收集不允许丢弃上下文,activity 等,因为周围仍然有一个活动引用。因此你正在泄漏内存。如果您在应用程序重新创建后访问此上下文(例如更新视图),您将收到一个运行时异常,告诉您您正在处理过时的 context/view。如果你真的想走那条路,你可能想看看 WeakReference
s。
非静态内部 classes 危险的另一个原因是它们从不提供默认构造函数。但是 Android 框架要求每个 Fragment 都有一个空的构造函数,以便在幕后实例化它。如果您的应用程序被拆除并重新启动(例如旋转更改),那么 Android 将通过强制默认构造函数重新实例化您的片段,然后通过序列化包恢复状态。如果您没有默认构造函数:运行时异常。您可以通过保留您的片段来解决这个问题,但我在 1) 中解释了为什么这也很糟糕。
故事的寓意:小心非静态内部 classes。
您的代码应该如何工作:
ActivityNeki.java
public class ActivityNeki extends FragmentActivity implements DatePickerDialog.OnDateSetListener
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
Fragment fragment = getSupportFragmentManager().findFragmentByTag( "my_dialog_tag" );
if( fragment != null )
{
( (MyDialogFragment) fragment ).listener = this;
}
}
// Called from xml
public void onClickOpenDPD(View view)
{
MyDialogFragment.newInstance( x, x, x, this ).show( getSupportFragmentManager(), "my_dialog_tag" );
}
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth)
{
// Do your crazy callback stuff
}
}
MyDialogFragment.java
class MyDialogFragment extends DialogFragment
{
public static MyDialogFragment newInstance( int datY, int datM, int datD, DatePickerDialog.OnDateSetListener listener )
{
Bundle bundle = new Bundle( 3 );
bundle.putInt( "y", datY );
bundle.putInt( "m", datM );
bundle.putInt( "d", datD );
MyDialogFragment fragment = new MyDialogFragment();
fragment.setArguments( bundle );
fragment.listener = listener;
return fragment;
}
public DatePickerDialog.OnDateSetListener listener = null;
// Not entirely sure if this is still necessary
@Override
public void onDestroyView()
{
if(getDialog() != null && getRetainInstance())
{
getDialog().setDismissMessage(null);
super.onDestroyView();
}
}
@Override
public Dialog onCreateDialog(Bundle state)
{
return new DatePickerDialog(
getActivity(),
listener,
getArguments().getInt( "y" ),
getArguments().getInt( "m" ),
getArguments().getInt( "d" )
);
}
}
请原谅小错误,因为我没有测试就凭空写下了这篇文章。
在打开的 DatePickerDialog 中,当屏幕方向改变时,它会重置所选的用户数据。
(DatePickerDialog 不会关闭并且不会维护所选数据)
代码:
public class ActivityNeki extends FragmentActivity {
DialogFragment newDF = null;
private int datY, datM, datD;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
if(savedInstanceState == null){ setTheData(); writeTheData(); }
}
@Override protected void onSaveInstanceState(Bundle outState) {
outState.putInt("izY", datY); outState.putInt("izM", datM); outState.putInt("izD", datD);
super.onSaveInstanceState(outState);
}
@Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState);
datY = savedInstanceState.getInt("izY"); datM = savedInstanceState.getInt("izM"); datD = savedInstanceState.getInt("izD");
writeTheData();
}
public void onClickOpenDPD(View view) { // the method that is caled from XML onClick
class MyDialogFragment extends DialogFragment {
@Override public void onDestroyView() {
if (getDialog() != null && getRetainInstance()) getDialog().setDismissMessage(null);
super.onDestroyView();
}
@Override public void onCreate(Bundle state) { super.onCreate(state);
setRetainInstance(true);
}
@Override public Dialog onCreateDialog(Bundle state) {
DatePickerDialog dpd = new DatePickerDialog( getActivity(), new DatePickerDialog.OnDateSetListener() {
@Override public void onDateSet(DatePicker view, int leto, int mesec, int dan) {
datY = leto; datM = mesec; datD = dan;
writeTheData();
} }, datY, datM, datD);
return dpd;
}
}
newDF = new MyDialogFragment();
newDF.show( getSupportFragmentManager(), null );
}
public void setTheData(){
Calendar c = Calendar.getInstance();
datY = c.get(Calendar.YEAR); datM = c.get(Calendar.MONTH); datD = c.get(Calendar.DAY_OF_MONTH);
}
public void writeTheData(){ /* writes the data in a txtView */ }
}
建议我,如何解决这个问题?
当方向改变时,应用程序实际上被销毁并重新创建。所以你必须在某处保存状态,例如在 instantState Bundle
-- 新编辑
我尝试了一种不同的方法,使用 Button 中的 OnClickListener 回调,并保留了改变方向的状态。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// the method that is called from XML onClick
class MyDialogFragment extends DialogFragment {
@Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
setRetainInstance(true);
}
@Override
public Dialog onCreateDialog(Bundle state) {
DatePickerDialog dpd = new DatePickerDialog(getActivity(), new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker view, int leto, int mesec, int dan) {
datY = leto;
datM = mesec;
datD = dan;
writeTheData();
}
}, datY, datM, datD);
return dpd;
}
}
newDF = new MyDialogFragment();
newDF.show(getFragmentManager(), null);
}
});
}
你的代码看起来很糟糕。
首先,您在方法主体中声明匿名片段 class - 但是,最好将此 class 与 Activity
class 分开或将其设为静态内部 class.
然后,你setRetainInstance(true)
,告诉Android不要在Activity旋转时破坏片段。这适用于其他用例,您的情况并非如此。
我建议您阅读以下信息:
How to communicate Activity with Fragments
基本上,如果您按照 Android 规则(见上面的链接)进行游戏,大部分用户输入将在设备旋转时立即保存。
正确实现的 DialogFragment 将能够恢复状态,而无需您进行任何额外的工作。您代码中的主要问题是非静态内部 class。这是有缺陷的,原因有两个:
一个非静态内部 class 自然地保持对其外部 class 的引用(参见 #2 中的 link)。这实际上是对您的 activity、您的上下文和观点的引用;基本上是用户所面对的一切。当设备旋转或用户切换到另一个应用程序时,您的应用程序很可能会关闭。但是,保留的片段将保持活动状态,这有时非常有用。但是因为在你的情况下它是一个内部 class 垃圾收集不允许丢弃上下文,activity 等,因为周围仍然有一个活动引用。因此你正在泄漏内存。如果您在应用程序重新创建后访问此上下文(例如更新视图),您将收到一个运行时异常,告诉您您正在处理过时的 context/view。如果你真的想走那条路,你可能想看看
WeakReference
s。非静态内部 classes 危险的另一个原因是它们从不提供默认构造函数。但是 Android 框架要求每个 Fragment 都有一个空的构造函数,以便在幕后实例化它。如果您的应用程序被拆除并重新启动(例如旋转更改),那么 Android 将通过强制默认构造函数重新实例化您的片段,然后通过序列化包恢复状态。如果您没有默认构造函数:运行时异常。您可以通过保留您的片段来解决这个问题,但我在 1) 中解释了为什么这也很糟糕。
故事的寓意:小心非静态内部 classes。
您的代码应该如何工作:
ActivityNeki.java
public class ActivityNeki extends FragmentActivity implements DatePickerDialog.OnDateSetListener
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
Fragment fragment = getSupportFragmentManager().findFragmentByTag( "my_dialog_tag" );
if( fragment != null )
{
( (MyDialogFragment) fragment ).listener = this;
}
}
// Called from xml
public void onClickOpenDPD(View view)
{
MyDialogFragment.newInstance( x, x, x, this ).show( getSupportFragmentManager(), "my_dialog_tag" );
}
public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth)
{
// Do your crazy callback stuff
}
}
MyDialogFragment.java
class MyDialogFragment extends DialogFragment
{
public static MyDialogFragment newInstance( int datY, int datM, int datD, DatePickerDialog.OnDateSetListener listener )
{
Bundle bundle = new Bundle( 3 );
bundle.putInt( "y", datY );
bundle.putInt( "m", datM );
bundle.putInt( "d", datD );
MyDialogFragment fragment = new MyDialogFragment();
fragment.setArguments( bundle );
fragment.listener = listener;
return fragment;
}
public DatePickerDialog.OnDateSetListener listener = null;
// Not entirely sure if this is still necessary
@Override
public void onDestroyView()
{
if(getDialog() != null && getRetainInstance())
{
getDialog().setDismissMessage(null);
super.onDestroyView();
}
}
@Override
public Dialog onCreateDialog(Bundle state)
{
return new DatePickerDialog(
getActivity(),
listener,
getArguments().getInt( "y" ),
getArguments().getInt( "m" ),
getArguments().getInt( "d" )
);
}
}
请原谅小错误,因为我没有测试就凭空写下了这篇文章。