如何在屏幕旋转时保留EditText中的数据?

31
我有一个登录界面,其中包含两个 EditText 分别用于输入用户名和密码。 我的要求是,在屏幕方向发生改变时,EditText 中的输入数据(如果有)应保持不变,同时还应绘制一个新的布局。我有两个 layout xml 文件- 一个在 layout 文件夹中,另一个在 layout-land 文件夹中。 我正在尝试实现以下两种方法,但都不完美:
(1) configChanges:keyboardHidden - 在此方法中,我不在清单文件中提供“屏幕方向”的 configChanges。因此,我在 onCreate() 和 onConfigurationChanged() 方法中均调用 setContentView() 方法。 它满足了我的两个要求。布局已更改,EditText 中的输入数据也保持不变。 但它有一个大问题:
当用户单击登录按钮时,会显示一个 ProgressDialog,直到收到服务器响应为止。 现在,如果用户在 ProgressDialog 运行时旋转设备,则应用程序崩溃。它显示一个异常,显示“无法将视图附加到窗口”。我尝试使用 onSaveInstanceState(确实在屏幕方向发生改变时调用)进行处理,但应用程序仍会崩溃。
(2) configChanges:orientation|keyboardHidden - 在此方法中,我在清单中提供“屏幕方向”。所以现在我有两种情况:
(a) 如果我在 onCreate() 和 onConfigurationChanged() 中均调用 setContentView() 方法,则布局会相应更改,但 EditText 数据会丢失。
(b) 如果我只在 onCreate() 中调用 setContentView() 方法,而不在 onConfigurationChanged() 中调用,则 EditText 数据不会丢失,但布局也不会相应更改。
在这种方法中,onSaveInstanceState() 甚至都没有被调用。
所以我真的很担心。有没有解决此问题的方法?请提供帮助。先感谢您。

1
@Doomsknight:你能详细说明一下吗? - Yogesh Somani
这里有一些好的想法/示例 https://dev59.com/lW435IYBdhLWcg3w1z4t 特别是Eric Nordvik的答案,如果其他方法不起作用。 - IAmGroot
@VijayC: 我还在尝试所有的方法。等我完成后会马上通知你。 - Yogesh Somani
12个回答

89

默认情况下,当改变方向时,Edittext会保存它们自己的实例。

请确保2个Edittext具有唯一的ID,并且在两个布局中具有相同的ID。

这样,它们的状态就应该被保存了,你可以让Android处理方向的变化。

如果您正在使用一个fragment,请确保它也有一个唯一的ID,并且在重新创建Activity时不要重新创建它。


1
当你说“确保片段有唯一的ID”时,你的意思是什么? - Graeme
每个视图都有一个ID(EditText和Fragments都是视图)。您需要确保此ID未被任何其他视图使用,因为在尝试自动保存和恢复状态时,这可能会导致问题。 - Yalla T.
3
如果您使用ListView呢?由于每个项没有唯一的ID,您会很不幸吗? - user2836797
如果EditText是在Java代码中构建的,则可以通过预定义的资源XML ID <item type="id" name="some_id">来设置ID,然后在Java中使用editText.setId(R.id.some_id) - Saqib
我创建了两个XML文件,一个在activity_main中,另一个在activity_main_land中。它们都有2个编辑文本字段。两个XML文件都有ID,并且在两个XML文件中ID相同。当调用onConfigchange()方法时,我更改XML文件。现在,当我旋转设备时,编辑文本会丢失数据。请问您能否帮助我解决这个问题? - Shivang
显示剩余2条评论

34
更好的方法是让Android处理屏幕方向变化。Android会自动从正确的文件夹中获取布局,并在屏幕上显示它。你所需要做的就是在onSaveInstanceState()方法中保存编辑文本框的输入值,并使用这些保存的值在onCreate()方法中初始化编辑文本框。以下是实现此目的的方法:
@Override
protected void onCreate (Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.login_screen);
    ...
    ...
    String userName, password;
    if(savedInstanceState!=null)
    {
        userName = savedInstanceState.getString("user_name");
        password= savedInstanceState.getString("password");
    }

    if(userName != null)
        userNameEdtTxt.setText(userName);
    if(password != null)
        passEdtTxt.setText(password);
}

>

@Override
    protected void onSaveInstanceState (Bundle outState)
    {
        outState.putString("user_name", userNameEdtTxt.getText().toString());
        outState.putString("password",  passEdtTxt.getText().toString());
    }

如果我在Android清单文件中处理方向,并使用onConfigurationChanged()方法,那么onCreate()会再次被调用吗? - Tamim Attafi
这是否保留了每个EditText中的光标位置和文本选择?Yalla T.所描述的唯一ID方法确实保留了光标位置和文本选择。 - Ted Henry

11

给元素一个id,Android会为您管理它。

android:id="@id/anything"

9

在 onConfigurationChanged 方法中,首先将两个编辑文本的数据存储到全局变量中,然后调用 setContentView 方法。现在再次将保存的数据设置回编辑文本中。


这是否保留每个EditText中的光标位置和文本选择?Yalla T.所描述的独特ID方法确实可以保留光标位置和文本选择。 - Ted Henry

2

有许多方法可以实现这一点。最简单的是在您的问题中的2(b)。在清单文件中提到 android:configChanges="orientation|keyboardHidden|screenSize",以便Activity在方向更改时不会被销毁。

onConfigChange()中调用setContentView()。但在调用setContentView()之前,将EditText数据获取为字符串,并在调用后设置回来。

 @Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    mEditTextData = mEditText.getText().tostring();//mEditTextData is a String 
                                                   //member variable
    setContentView(R.layout.myLayout);
    initializeViews();
}

private void initializeViews(){
    mEditText = (EditText)findViewById(R.id.edittext1);
    mEdiText.setText(mEditTextData);
}

2
以下内容是标准的活动和片段操作方式,应该能够正常工作。
@Override
public void onSaveInstanceState (Bundle outState) 
{
     outState.putString("editTextData1", editText1.getText().toString());
     outState.putString("editTextData2", editText2.getText().toString());

     super.onSaveInstanceState(outState);
}

@Override
public void onCreate(Bundle savedInstanceState)
{
      super.onCreate();

      ... find references to editText1, editText2

      if (savedInstanceState != null)
      {
           editText1.setText(savedInstanceState.getString("editTextData1");
           editText2.setText(savedInstanceState.getString("editTextData2");
      }
}

2

我正在使用“还原实例”功能来恢复数值,它对我来说很有效 :)

@Override
protected void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    setContentView(R.layout.addtask2);
    if(savedInstanceState!=null)
     onRestoreInstanceState(savedInstanceState);

}

1

从menifest文件中删除android:configChanges属性,让Android处理方向更改,您在edittext中的数据将自动保留。

现在,您提到的问题是进度对话框强制关闭,这是因为当方向更改时,后台运行的线程正在尝试更新旧的对话框组件,而该组件是可见的。您可以通过在savedinstancestate方法中关闭对话框,并在onRestoreInstanceState方法中重新调用要执行的进程来处理它。

以下是一个示例,希望它有助于解决您的问题:

public class MyActivity extends Activity {
    private static final String TAG = "com.example.handledataorientationchange.MainActivity";
    private static ProgressDialog progressDialog;
    private static Thread thread;
    private static boolean isTaskRunnig;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.d(TAG, "onCreate");
        setContentView(R.layout.main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new EditText.OnClickListener() {

            @Override
            public void onClick(View v) {
                perform();
                isTaskRunnig = true;
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    public void perform() {
        Log.d(TAG, "perform");
        progressDialog = android.app.ProgressDialog.show(this, null,
                "Working, please wait...");
        progressDialog
                .setOnDismissListener(new DialogInterface.OnDismissListener() {

                    @Override
                    public void onDismiss(DialogInterface dialog) {
                        //isTaskRunnig = false;
                    }
                });
        thread = new Thread() {
            public void run() {
                Log.d(TAG, "run");
                int result = 0;
                try {

                    // Thread.sleep(5000);
                    for (int i = 0; i < 20000000; i++) {

                    }
                    result = 1;
                    isTaskRunnig = false;
                } catch (Exception e) {
                    e.printStackTrace();
                    result = 0;
                }
                Message msg = new Message();
                msg.what = result;
                handler.sendMessage(msg);
            };
        };
        thread.start();
    }

    // handler to update the progress dialgo while the background task is in
    // progress
    private static Handler handler = new Handler() {

        public void handleMessage(Message msg) {
            Log.d(TAG, "handleMessage");
            int result = msg.what;
            if (result == 1) {// if the task is completed successfully
                Log.d(TAG, "Task complete");
                try {
                    progressDialog.dismiss();
                } catch (Exception e) {
                    e.printStackTrace();
                    isTaskRunnig = true;
                }

            }

        }
    };

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        Log.d(TAG, "onRestoreInstanceState" + isTaskRunnig);
        if (isTaskRunnig) {
            perform();

        }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        Log.d(TAG, "onSaveInstanceState");
        if (thread.isAlive()) {
            thread.interrupt();
            Log.d(TAG, thread.isAlive() + "");
            progressDialog.dismiss();
        }

    }

不要使用 static 来保留引用,可以使用 Activity#onRetainNonConfigurationInstance() 或在此处描述的保留片段方法来实现该目的。 - zapl

1
正如Yalla T所指出的那样,重建片段是不必要的。如果重用现有片段,则EditText不会丢失其内容。
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // setContentView(R.layout.activity_frame);
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);

    // Display the fragment as the main content.
    // Do not do this. It will recreate the fragment on orientation change!
    // getSupportFragmentManager().beginTransaction().replace(android.R.id.content, new Fragment_Places()).commit();

    // Instead do this
    String fragTag = "fragUniqueName";
    FragmentManager fm = getSupportFragmentManager();
    Fragment fragment = (Fragment) fm.findFragmentByTag(fragTag);
    if (fragment == null)
        fragment = new Fragment_XXX(); // Here your fragment
    FragmentTransaction ft = fm.beginTransaction();
    // ft.setCustomAnimations(R.xml.anim_slide_in_from_right, R.xml.anim_slide_out_left,
    // R.xml.anim_slide_in_from_left, R.xml.anim_slide_out_right);
    ft.replace(android.R.id.content, fragment, fragTag);
    // ft.addToBackStack(null); // Depends on what you want to do with your back button
    ft.commit();

}

1

enter image description here

保存状态 = 保存(片段状态 + 活动状态)

当涉及到在屏幕方向改变时保存Fragment状态时,我通常会这样做。

1)片段状态:

保存和恢复EditText的值

// Saving State

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putString("USER_NAME", username.getText().toString());
    outState.putString("PASSWORD", password.getText().toString());
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {

    View view = inflater.inflate(R.layout.user_name_fragment, parent, false);

    username = (EditText) view.findViewById(R.id.username);
    password = (EditText) view.findViewById(R.id.password);


// Retriving value

    if (savedInstanceState != null) {
        username.setText(savedInstanceState.getString("USER_NAME"));
        password.setText(savedInstanceState.getString("PASSWORD"));
    }

    return view;
}

2) 活动状态::

当活动首次启动时,创建一个新实例,否则使用TAGFragmentManager查找旧的片段。

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main_activity);

    fragmentManager = getSupportFragmentManager();

    if(savedInstanceState==null) {
        userFragment = UserNameFragment.newInstance();
        fragmentManager.beginTransaction().add(R.id.profile, userFragment, "TAG").commit();
    }
    else {
        userFragment = fragmentManager.findFragmentByTag("TAG");
    }

}

你可以在这里看到完整的工作代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接